3
3
# Copyright (C) 2022 DavidoTek, partially based on AUNaseef's protonup
4
4
5
5
import os
6
- import requests
7
6
8
- from PySide6 .QtCore import QObject , QCoreApplication , Signal , Property
7
+ from PySide6 .QtCore import QCoreApplication
9
8
10
- from pupgui2 .util import ghapi_rlcheck , extract_zip
11
- from pupgui2 .util import build_headers_with_authorization
9
+ from pupgui2 .util import extract_zip , ghapi_rlcheck
10
+
11
+ from pupgui2 .resources .ctmods .ctmod_z0dxvk import CtInstaller as DXVKInstaller
12
12
13
13
14
14
CT_NAME = 'DXVK (nightly)'
15
15
CT_LAUNCHERS = ['lutris' , 'advmode' ]
16
16
CT_DESCRIPTION = {'en' : QCoreApplication .instance ().translate ('ctmod_z2dxvknightly' , '''Nightly version of DXVK (master branch), a Vulkan based implementation of Direct3D 8, 9, 10 and 11 for Linux/Wine.<br/><br/><b>Warning: Nightly version is unstable, use with caution!</b>''' )}
17
17
18
18
19
- class CtInstaller (QObject ):
19
+ class CtInstaller (DXVKInstaller ):
20
20
21
- BUFFER_SIZE = 65536
22
- CT_URL = 'https://api.github.com/repos/doitsujin/dxvk/actions/artifacts'
23
- CT_INFO_URL = 'https://github.com/doitsujin/dxvk/commit/'
21
+ BUFFER_SIZE : int = 65536
22
+ CT_WORKFLOW_URL : str = 'https://api.github.com/repos/doitsujin/dxvk/actions/workflows'
23
+ CT_ARTIFACT_URL : str = 'https://api.github.com/repos/doitsujin/dxvk/actions/runs/{}/artifacts'
24
+ CT_ALL_ARTIFACTS_URL : str = 'https://api.github.com/repos/doitsujin/dxvk/actions/artifacts'
25
+ CT_INFO_URL : str = 'https://github.com/doitsujin/dxvk/commit/'
24
26
25
- p_download_progress_percent = 0
26
- download_progress_percent = Signal (int )
27
+ DXVK_WORKFLOW_NAME : str = 'artifacts'
27
28
28
29
def __init__ (self , main_window = None ):
29
- super (CtInstaller , self ).__init__ ()
30
- self .p_download_canceled = False
31
30
32
- self . rs = requests . Session ( )
33
- rs_headers = build_headers_with_authorization ({}, main_window . web_access_tokens , 'github' )
34
- self .rs . headers . update ( rs_headers )
31
+ super (). __init__ ( main_window )
32
+
33
+ self .release_format : str = 'zip'
35
34
36
- def get_download_canceled (self ):
37
- return self .p_download_canceled
35
+ def __fetch_workflows (self , count : int = 30 ) -> list [str ]:
38
36
39
- def set_download_canceled (self , val ):
40
- self .p_download_canceled = val
37
+ """
38
+ Get all active, successful runs in the DXVK Linux-compatible workflow.
39
+ Return Type: list
40
+ """
41
41
42
- download_canceled = Property (bool , get_download_canceled , set_download_canceled )
42
+ workflow_request_url : str = f'{ self .CT_WORKFLOW_URL } ?per_page={ str (count )} '
43
+ workflow_response_json : dict = self .rs .get (workflow_request_url ).json ()
43
44
44
- def __set_download_progress_percent (self , value : int ):
45
- if self .p_download_progress_percent == value :
46
- return
47
- self .p_download_progress_percent = value
48
- self .download_progress_percent .emit (value )
45
+ tags : list [str ] = []
46
+ for workflow in workflow_response_json .get ('workflows' , {}):
47
+ if workflow ['state' ] != "active" or self .DXVK_WORKFLOW_NAME not in workflow ['path' ]:
48
+ continue
49
+
50
+ page = 1
51
+ while page != - 1 and page <= 5 : # fetch more (up to 5 pages) if first releases all failed
52
+ at_least_one_failed = False # ensure the reason that len(tags)=0 is that releases failed
53
+
54
+ workflow_runs_request_url : str = f'{ workflow ["url" ]} /runs?per_page={ count } &page={ page } '
55
+ workflow_runs_response_json : dict = self .rs .get (workflow_runs_request_url ).json ()
56
+
57
+ for run in workflow_runs_response_json .get ('workflow_runs' , {}):
58
+ if run ['head_branch' ] != 'master' :
59
+ continue
60
+
61
+ if run ['conclusion' ] == "failure" :
62
+ at_least_one_failed = True
63
+
64
+ continue
65
+
66
+ # TODO can make this generic so that i.e. this DXVK Ctmod can use commmit SHAs but Proton-tkg can use workflow IDs?
67
+ # then this could be a generic function shared between ctmods and could be in a util file, unit tested, etc
68
+ commit_hash : str = str (run ['head_commit' ]['id' ][:7 ])
69
+ tags .append (commit_hash )
70
+
71
+ if len (tags ) == 0 and at_least_one_failed :
72
+ page += 1
73
+
74
+ continue
75
+
76
+ page = - 1
77
+
78
+ return tags
79
+
80
+ def fetch_releases (self , count : int = 30 , page : int = 1 ) -> list [str ]:
49
81
50
- def __download (self , url , destination , f_size ):
51
- # f_size in argumentbecause artifacts don't have Content-Length.
52
82
"""
53
- Download files from url to destination
54
- Return Type: bool
83
+ List available releases.
84
+ Return Type: str[]
55
85
"""
56
- try :
57
- file = requests .get (url , stream = True )
58
- except OSError :
59
- return False
60
86
61
- self .__set_download_progress_percent (1 ) # 1 download started
62
- c_count = int (f_size / self .BUFFER_SIZE )
63
- c_current = 1
64
- destination = os .path .expanduser (destination )
65
- os .makedirs (os .path .dirname (destination ), exist_ok = True )
66
- with open (destination , 'wb' ) as dest :
67
- for chunk in file .iter_content (chunk_size = self .BUFFER_SIZE ):
68
- if self .download_canceled :
69
- self .download_canceled = False
70
- self .__set_download_progress_percent (- 2 ) # -2 download canceled
71
- return False
72
- if chunk :
73
- dest .write (chunk )
74
- dest .flush ()
75
- self .__set_download_progress_percent (int (min (c_current / c_count * 98.0 , 98.0 ))) # 1-98, 100 after extract
76
- c_current += 1
77
- self .__set_download_progress_percent (99 ) # 99 download complete
78
- return True
87
+ return self .__fetch_workflows (count = count )
79
88
80
89
def __get_artifact_from_commit (self , commit ):
90
+
81
91
"""
82
92
Get artifact from commit
83
93
Return Type: str
84
94
"""
85
- for artifact in self .rs .get (f'{ self .CT_URL } ?per_page=100' ).json ()["artifacts" ]:
86
- if artifact ['workflow_run' ]['head_sha' ][:len (commit )] == commit :
95
+
96
+ for artifact in self .rs .get (f'{ self .CT_ALL_ARTIFACTS_URL } ?per_page=100' ).json ()["artifacts" ]:
97
+ # DXVK appends '-msvc-output' to Windows builds
98
+ # See: https://github.com/doitsujin/dxvk/blob/20a6fae8a7f60e7719724b229552eba1ae6c3427/.github/workflows/test-build-windows.yml#L80
99
+ if artifact ['workflow_run' ]['head_sha' ][:len (commit )] == commit and not artifact ['name' ].endswith ('-msvc-output' ):
87
100
artifact ['workflow_run' ]['head_sha' ] = commit
88
101
return artifact
102
+
89
103
return None
90
104
91
- def __fetch_github_data (self , tag ):
105
+ def __fetch_github_data (self , tag : str ):
106
+
92
107
"""
93
108
Fetch GitHub release information
94
109
Return Type: dict
95
110
Content(s):
96
- 'version', 'date', 'download', 'size', 'checksum'
111
+ 'version', 'date', 'download', 'size'
97
112
"""
98
- # Tag in this case is the commit hash.
113
+
114
+ # Tag in this case is the commit hash
99
115
data = self .__get_artifact_from_commit (tag )
100
116
if not data :
101
117
return
@@ -105,50 +121,39 @@ def __fetch_github_data(self, tag):
105
121
values ['size' ] = data ['size_in_bytes' ]
106
122
return values
107
123
108
- def is_system_compatible (self ):
109
- """
110
- Are the system requirements met?
111
- Return Type: bool
112
- """
113
- return True
124
+ def __get_data (self , version : str , install_dir : str ) -> tuple [dict | None , str | None ]:
114
125
115
- def fetch_releases (self , count = 100 , page = 1 ):
116
126
"""
117
- List available releases
118
- Return Type: str[ ]
127
+ Get needed download data and path to extract directory.
128
+ Return Type: diple[dict | None, str | None ]
119
129
"""
120
- tags = []
121
- for artifact in ghapi_rlcheck (self .rs .get (f'{ self .CT_URL } ?per_page={ count } &page={ page } ' ).json ()).get ("artifacts" , {}):
122
- workflow = artifact ['workflow_run' ]
123
- if workflow ["head_branch" ] != "master" or artifact ["expired" ]:
124
- continue
125
- tags .append (workflow ['head_sha' ][:7 ])
126
- return tags
127
130
128
- def get_tool (self , version , install_dir , temp_dir ):
131
+ data = self .__fetch_github_data (version )
132
+ if not data or not 'download' in data :
133
+ return (None , None )
134
+
135
+ # TODO This is hardcoded to Lutris as DXVK Nightly currently doesn't support any other launchers -- Could possibly add support for Heroic in future
136
+ dxvk_dir = os .path .join (install_dir , '../../runtime/dxvk' , 'dxvk-git-' + data ['version' ])
137
+
138
+ return (data , dxvk_dir )
139
+
140
+ def __extract (self , archive_path : str , extract_dir : str ) -> bool :
141
+
129
142
"""
130
- Download and install the compatibility tool
143
+ Extract the tool archive at the given path.
131
144
Return Type: bool
132
145
"""
133
- data = self .__fetch_github_data (version )
134
- if not data or 'download' not in data :
135
- return False
136
146
137
- dxvk_zip = os .path .join (temp_dir , data ['download' ].split ('/' )[- 1 ])
138
- if not self .__download (url = data ['download' ], destination = dxvk_zip , f_size = data ['size' ]):
147
+ if not archive_path or not extract_dir :
139
148
return False
140
149
141
- dxvk_dir = os .path .join (install_dir , '../../runtime/dxvk' , 'dxvk-git-' + data ['version' ])
142
- if not extract_zip (dxvk_zip , dxvk_dir ):
143
- return False
150
+ return extract_zip (archive_path , extract_dir )
144
151
145
- self . __set_download_progress_percent ( 100 )
152
+ def get_info_url ( self , version : str ) -> str :
146
153
147
- return True
148
-
149
- def get_info_url (self , version ):
150
154
"""
151
155
Get link with info about version (eg. GitHub release page)
152
156
Return Type: str
153
157
"""
158
+
154
159
return self .CT_INFO_URL + version
0 commit comments