Skip to content

Commit 2d3314f

Browse files
authored
Merge pull request #42 from Precioussheep/feature/vsc-refactor
Refactor `vscodeoffline/vsc.py`
2 parents 8fbbadb + 35eb462 commit 2d3314f

3 files changed

Lines changed: 93 additions & 85 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ artifacts/*
22
__pycache__/
33
temp/
44
venv/
5-
.vscode/*
5+
.vscode/*
6+
.env

vscoffline/server.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,10 @@ def on_get(self, req, resp, platform, buildquality, commitid):
2929
resp.status = falcon.HTTP_204
3030
return
3131
name = latest['name']
32-
updateglob = os.path.join(updatedir, f'vscode-{name}.*')
33-
updatepath = vsc.Utility.first_file(updateglob)
32+
updatepath = vsc.Utility.first_file(updatedir, f'vscode-{name}.*')
3433
if not updatepath:
3534
resp.content = 'Unable to find update payload'
36-
log.warning(f'Unable to find update payload from {updateglob}')
35+
log.warning(f'Unable to find update payload from {updatedir}/vscode-{name}.*')
3736
resp.status = falcon.HTTP_404
3837
return
3938
if not vsc.Utility.hash_file_and_check(updatepath, latest['sha256hash']):
@@ -63,10 +62,9 @@ def on_get(self, req, resp, commitid, platform, buildquality):
6362
resp.status = falcon.HTTP_500
6463
return
6564
name = updatejson['name']
66-
updateglob = os.path.join(updatedir, f'vscode-{name}.*')
67-
updatepath = vsc.Utility.first_file(updateglob)
65+
updatepath = vsc.Utility.first_file(updatedir, f'vscode-{name}.*')
6866
if not updatepath:
69-
resp.content = f'Unable to find update payload from {updateglob}'
67+
resp.content = f'Unable to find update payload from {updatedir}/vscode-{name}.*'
7068
log.warning(resp.content)
7169
resp.status = falcon.HTTP_404
7270
return

vscoffline/vsc.py

Lines changed: 87 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
1-
import os
2-
import io
3-
import json
4-
import hashlib
5-
import glob
61
import datetime
2+
import hashlib
3+
import json
4+
import os
5+
import pathlib
76
from enum import IntFlag
7+
from typing import Any, Dict, List, Union
8+
89
from logzero import logger as log
910

10-
PLATFORMS = ['win32', 'linux', 'linux-deb', 'linux-rpm',
11-
'darwin', 'linux-snap', 'server-linux']
12-
ARCHITECTURES = ['', 'x64']
13-
BUILDTYPES = ['', 'archive', 'user']
14-
QUALITIES = ['stable', 'insider']
11+
PLATFORMS = ["win32", "linux", "linux-deb", "linux-rpm", "darwin", "linux-snap", "server-linux"]
12+
ARCHITECTURES = ["", "x64"]
13+
BUILDTYPES = ["", "archive", "user"]
14+
QUALITIES = ["stable", "insider"]
1515

16-
URL_BINUPDATES = r'https://update.code.visualstudio.com/api/update/'
17-
URL_RECOMMENDATIONS = r'https://az764295.vo.msecnd.net/extensions/workspaceRecommendations.json.gz'
18-
URL_MARKETPLACEQUERY = r'https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery'
19-
URL_MALICIOUS = r'https://az764295.vo.msecnd.net/extensions/marketplace.json'
16+
URL_BINUPDATES = r"https://update.code.visualstudio.com/api/update/"
17+
URL_RECOMMENDATIONS = r"https://az764295.vo.msecnd.net/extensions/workspaceRecommendations.json.gz"
18+
URL_MARKETPLACEQUERY = r"https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery"
19+
URL_MALICIOUS = r"https://az764295.vo.msecnd.net/extensions/marketplace.json"
2020

21-
URLROOT = 'https://update.code.visualstudio.com'
22-
ARTIFACTS = '/artifacts/'
23-
ARTIFACTS_INSTALLERS = '/artifacts/installers'
24-
ARTIFACTS_EXTENSIONS = '/artifacts/extensions'
25-
ARTIFACT_RECOMMENDATION = '/artifacts/recommendations.json'
26-
ARTIFACT_MALICIOUS = '/artifacts/malicious.json'
21+
URLROOT = "https://update.code.visualstudio.com"
22+
ARTIFACTS = "/artifacts/"
23+
ARTIFACTS_INSTALLERS = "/artifacts/installers"
24+
ARTIFACTS_EXTENSIONS = "/artifacts/extensions"
25+
ARTIFACT_RECOMMENDATION = "/artifacts/recommendations.json"
26+
ARTIFACT_MALICIOUS = "/artifacts/malicious.json"
2727

2828
TIMEOUT = 12
2929

3030

3131
class QueryFlags(IntFlag):
32-
__no_flags_name__ = 'NoneDefined'
32+
__no_flags_name__ = "NoneDefined"
3333
NoneDefined = 0x0
3434
IncludeVersions = 0x1
3535
IncludeFiles = 0x2
@@ -45,7 +45,7 @@ class QueryFlags(IntFlag):
4545

4646

4747
class FilterType(IntFlag):
48-
__no_flags_name__ = 'Target'
48+
__no_flags_name__ = "Target"
4949
Tag = 1
5050
ExtensionId = 4
5151
Category = 5
@@ -58,7 +58,7 @@ class FilterType(IntFlag):
5858

5959

6060
class SortBy(IntFlag):
61-
__no_flags_name__ = 'NoneOrRelevance'
61+
__no_flags_name__ = "NoneOrRelevance"
6262
NoneOrRelevance = 0
6363
LastUpdatedDate = 1
6464
Title = 2
@@ -70,109 +70,118 @@ class SortBy(IntFlag):
7070

7171

7272
class SortOrder(IntFlag):
73-
__no_flags_name__ = 'Default'
73+
__no_flags_name__ = "Default"
7474
Default = 0
7575
Ascending = 1
7676
Descending = 2
7777

7878

7979
class MagicJsonEncoder(json.JSONEncoder):
80-
def default(self, o):
81-
if isinstance(o, datetime.datetime):
82-
return o.isoformat()
83-
return o.__dict__
80+
def default(self, o: Any) -> Union[str, Dict[str, Any]]:
81+
try:
82+
return super().default(o)
83+
except TypeError as err:
84+
# could be datetime
85+
if isinstance(o, datetime.datetime):
86+
return o.isoformat()
87+
# could also be cls with slots
88+
try:
89+
return {key: getattr(o, key, None) for key in o.__slots__}
90+
except AttributeError:
91+
pass
92+
# finally, should have a dict if it is a dataclass or another cls
93+
try:
94+
return o.__dict__
95+
except AttributeError:
96+
raise TypeError(
97+
"Can't encode object. Tried isoformat of datetime, class slots and class dict"
98+
) from err
8499

85100

86-
class Utility(object):
101+
class Utility:
87102
"""
88103
Utility tool
89104
"""
90105

91106
@staticmethod
92-
def hash_file_and_check(filepath, expectedchecksum):
107+
def hash_file_and_check(filepath: Union[str, pathlib.Path], expectedchecksum: str) -> bool:
93108
"""
94-
Hashes a file and checks for the expected checksum
109+
Hashes a file and checks for the expected checksum.
110+
Checksum is sha256 default implementation.
95111
"""
96112
h = hashlib.sha256()
97-
with open(filepath, 'rb') as f:
98-
for chunk in iter(lambda: f.read(4096), b''):
113+
with open(filepath, "rb") as f:
114+
for chunk in iter(lambda: f.read(4096), b""):
99115
h.update(chunk)
100-
if expectedchecksum != h.hexdigest():
101-
return False
102-
103-
return True
116+
return expectedchecksum == h.hexdigest()
104117

105118
@staticmethod
106-
def load_json(filepath):
119+
def load_json(filepath: Union[str, pathlib.Path]) -> Union[List[Any], Dict[str, Any]]:
120+
if isinstance(filepath, str):
121+
filepath: pathlib.Path = pathlib.Path(filepath)
122+
107123
result = []
108-
if not os.path.exists(filepath):
109-
log.debug(f'Unable to load json from {filepath}')
110-
return []
111-
with io.open(filepath, 'r', encoding='utf-8-sig') as fp:
124+
if not filepath.exists():
125+
log.debug(f"Unable to load json from {filepath.absolute()}. Does not exist.")
126+
return result
127+
elif filepath.is_dir():
128+
log.debug(f"Cannot load json at path {filepath.absolute()}. It is a directory")
129+
return result
130+
131+
with open(filepath, "r", encoding="utf-8-sig") as fp:
112132
try:
113133
result = json.load(fp)
114134
if not result:
115135
return []
116-
except json.decoder.JSONDecodeError:
117-
log.debug(f'JSONDecodeError while processing {filepath}')
136+
except json.decoder.JSONDecodeError as err:
137+
log.debug(f"JSONDecodeError while processing {filepath.absolute()} \n error: {str(err)}")
118138
return []
119139
return result
120140

121141
@staticmethod
122-
def write_json(filepath, content):
123-
with open(filepath, 'w') as outfile:
142+
def write_json(filepath: Union[str, pathlib.Path], content: Dict[str, Any]) -> None:
143+
with open(filepath, "w") as outfile:
124144
json.dump(content, outfile, cls=MagicJsonEncoder, indent=4)
125145

126146
@staticmethod
127-
def first_file(filepath, reverse=False):
128-
results = glob.glob(filepath)
129-
if reverse:
147+
def first_file(filepath: Union[str, pathlib.Path], pattern: str, reverse: bool = False) -> Union[str, bool]:
148+
if isinstance(filepath, str):
149+
filepath = pathlib.Path(filepath)
150+
results = [*filepath.glob(pattern)]
151+
if not results:
152+
return False
153+
elif len(results) >= 1 and reverse:
130154
results.sort(reverse=True)
131-
# log.info(filepath)
132-
if results and len(results) >= 1:
133-
return results[0]
134-
return False
155+
return str(results[0].absolute())
135156

136157
@staticmethod
137-
def folders_in_folder(filepath):
158+
def folders_in_folder(filepath: str) -> List[str]:
138159
return [f for f in os.listdir(filepath) if os.path.isdir(os.path.join(filepath, f))]
139160

140161
@staticmethod
141-
def files_in_folder(filepath):
162+
def files_in_folder(filepath: str) -> List[str]:
142163
return [f for f in os.listdir(filepath) if os.path.isfile(os.path.join(filepath, f))]
143164

144165
@staticmethod
145-
def seconds_to_human_time(seconds):
166+
def seconds_to_human_time(seconds: int) -> str:
146167
return str(datetime.timedelta(seconds=seconds))
147168

148169
@staticmethod
149-
def from_json_datetime(jsondate):
150-
datetime.datetime.strptime(jsondate, '%Y-%m-%dT%H:%M:%S.%fZ')
170+
def from_json_datetime(jsondate: str) -> datetime.datetime:
171+
return datetime.datetime.strptime(jsondate, "%Y-%m-%dT%H:%M:%S.%fZ")
151172

152173
@staticmethod
153-
def validate_platform(platform):
154-
if platform in PLATFORMS:
155-
return True
156-
else:
157-
return False
174+
def validate_platform(platform: str) -> bool:
175+
return platform in PLATFORMS
158176

159177
@staticmethod
160-
def validate_architecture(arch):
161-
if arch in ARCHITECTURES:
162-
return True
163-
else:
164-
return False
178+
def validate_architecture(arch: str) -> bool:
179+
return arch in ARCHITECTURES
165180

166181
@staticmethod
167-
def validate_buildtype(buildtype):
168-
if buildtype in BUILDTYPES:
169-
return True
170-
else:
171-
return False
182+
def validate_buildtype(buildtype: str) -> bool:
183+
return buildtype in BUILDTYPES
172184

173185
@staticmethod
174-
def validate_quality(quality):
175-
if quality in QUALITIES:
176-
return True
177-
else:
178-
return False
186+
def validate_quality(quality: str) -> bool:
187+
return quality in QUALITIES

0 commit comments

Comments
 (0)