aether-cache 0.1.3__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aether_cache-0.1.3 → aether_cache-0.1.4}/PKG-INFO +8 -4
- {aether_cache-0.1.3 → aether_cache-0.1.4}/README.md +7 -3
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/__init__.py +1 -1
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/cli.py +37 -4
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/engine.py +36 -14
- aether_cache-0.1.4/aether_cache/javart.py +174 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/pyproject.toml +1 -1
- {aether_cache-0.1.3 → aether_cache-0.1.4}/.gitignore +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/__main__.py +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/config.py +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/connect.py +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/engine/aether-engine.jar +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/portability.py +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/repos.py +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/server.py +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/fonts/OFL.txt +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/index.html +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui.py +0 -0
- {aether_cache-0.1.3 → aether_cache-0.1.4}/docs/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: aether-cache
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Shared memory for AI coding tools that stays true to your code.
|
|
5
5
|
Project-URL: Homepage, https://anishchugh05.github.io/Aether-Cache-Redis-Compatible-Vector-Store/
|
|
6
6
|
Project-URL: Repository, https://github.com/anishchugh05/Aether-Cache-Redis-Compatible-Vector-Store
|
|
@@ -104,9 +104,8 @@ m1 retired; m3 replaces it.
|
|
|
104
104
|
|
|
105
105
|
## Quick start
|
|
106
106
|
|
|
107
|
-
You need **
|
|
108
|
-
|
|
109
|
-
Python for you. Then:
|
|
107
|
+
You need [**uv**](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh`), which also takes
|
|
108
|
+
care of Python for you. Then:
|
|
110
109
|
|
|
111
110
|
```sh
|
|
112
111
|
uv tool install aether-cache
|
|
@@ -121,6 +120,10 @@ repo and try:
|
|
|
121
120
|
|
|
122
121
|
Then start a new session and ask how to run the tests.
|
|
123
122
|
|
|
123
|
+
The engine runs on Java 11+. If you have it, it's used. If you don't, `setup` downloads a private copy (about
|
|
124
|
+
50 MB) into `~/.aether-cache/java`, checks it against its published checksum, and uses it only for Aether Cache;
|
|
125
|
+
nothing is installed system-wide, and `aether-cache uninstall` removes it.
|
|
126
|
+
|
|
124
127
|
Already keep notes for your AI in `CLAUDE.md`, `AGENTS.md`, or Cursor rules? Run `aether-cache import` in the repo
|
|
125
128
|
(or use **Import notes** in the dashboard) to turn them into memories, linked to the files they mention.
|
|
126
129
|
|
|
@@ -354,6 +357,7 @@ src/main/java/Main.java the engine (single file, from the CodeCrafters setu
|
|
|
354
357
|
aether_cache/ the Python package behind the `aether-cache` command
|
|
355
358
|
server.py MCP server: embeddings, git staleness, AGENTS.md/CLAUDE.md sync
|
|
356
359
|
engine.py finds Java, starts and stops the engine in the background
|
|
360
|
+
javart.py downloads a private copy of Java when the computer has none
|
|
357
361
|
connect.py registers Aether Cache with Claude Code, Cursor, and Codex
|
|
358
362
|
cli.py the `aether-cache` command
|
|
359
363
|
ui.py, ui/index.html the dashboard (`aether-cache ui`): a local server, its API, and the page
|
|
@@ -91,9 +91,8 @@ m1 retired; m3 replaces it.
|
|
|
91
91
|
|
|
92
92
|
## Quick start
|
|
93
93
|
|
|
94
|
-
You need **
|
|
95
|
-
|
|
96
|
-
Python for you. Then:
|
|
94
|
+
You need [**uv**](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh`), which also takes
|
|
95
|
+
care of Python for you. Then:
|
|
97
96
|
|
|
98
97
|
```sh
|
|
99
98
|
uv tool install aether-cache
|
|
@@ -108,6 +107,10 @@ repo and try:
|
|
|
108
107
|
|
|
109
108
|
Then start a new session and ask how to run the tests.
|
|
110
109
|
|
|
110
|
+
The engine runs on Java 11+. If you have it, it's used. If you don't, `setup` downloads a private copy (about
|
|
111
|
+
50 MB) into `~/.aether-cache/java`, checks it against its published checksum, and uses it only for Aether Cache;
|
|
112
|
+
nothing is installed system-wide, and `aether-cache uninstall` removes it.
|
|
113
|
+
|
|
111
114
|
Already keep notes for your AI in `CLAUDE.md`, `AGENTS.md`, or Cursor rules? Run `aether-cache import` in the repo
|
|
112
115
|
(or use **Import notes** in the dashboard) to turn them into memories, linked to the files they mention.
|
|
113
116
|
|
|
@@ -341,6 +344,7 @@ src/main/java/Main.java the engine (single file, from the CodeCrafters setu
|
|
|
341
344
|
aether_cache/ the Python package behind the `aether-cache` command
|
|
342
345
|
server.py MCP server: embeddings, git staleness, AGENTS.md/CLAUDE.md sync
|
|
343
346
|
engine.py finds Java, starts and stops the engine in the background
|
|
347
|
+
javart.py downloads a private copy of Java when the computer has none
|
|
344
348
|
connect.py registers Aether Cache with Claude Code, Cursor, and Codex
|
|
345
349
|
cli.py the `aether-cache` command
|
|
346
350
|
ui.py, ui/index.html the dashboard (`aether-cache ui`): a local server, its API, and the page
|
|
@@ -17,11 +17,38 @@ def _say(mark: str, text: str) -> None:
|
|
|
17
17
|
|
|
18
18
|
# ---------- commands ----------
|
|
19
19
|
|
|
20
|
+
def _java_label(java: str) -> str:
|
|
21
|
+
return f"private copy in {java_dir()}" if engine.is_private(java) else java
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def java_dir():
|
|
25
|
+
from .javart import JAVA_DIR
|
|
26
|
+
return JAVA_DIR
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _java_for_setup():
|
|
30
|
+
"""The computer's own Java if it has a recent enough one; otherwise downloads a private copy."""
|
|
31
|
+
try:
|
|
32
|
+
return engine.find_java()
|
|
33
|
+
except engine.EngineError as missing:
|
|
34
|
+
from . import javart
|
|
35
|
+
reason = str(missing).splitlines()[0] # "Java isn't installed." or "Found Java 8, which is too old."
|
|
36
|
+
print(f"[ .... ] {reason} Downloading a private copy of Java {javart.FEATURE_VERSION} for Aether Cache "
|
|
37
|
+
f"(about 50 MB, only the first time).\n"
|
|
38
|
+
f" It goes in {javart.JAVA_DIR}, only Aether Cache uses it, and nothing else on your "
|
|
39
|
+
f"computer changes.", flush=True)
|
|
40
|
+
try:
|
|
41
|
+
javart.download()
|
|
42
|
+
except javart.JavaDownloadError as e:
|
|
43
|
+
raise engine.EngineError(str(e))
|
|
44
|
+
return engine.find_java()
|
|
45
|
+
|
|
46
|
+
|
|
20
47
|
def cmd_setup(args) -> int:
|
|
21
48
|
print(f"Setting up Aether Cache {__version__}\n")
|
|
22
49
|
try:
|
|
23
|
-
java, major =
|
|
24
|
-
_say(OK, f"Java {major} ({java})")
|
|
50
|
+
java, major = _java_for_setup()
|
|
51
|
+
_say(OK, f"Java {major} ({_java_label(java)})")
|
|
25
52
|
_say(OK, engine.start())
|
|
26
53
|
except engine.EngineError as e:
|
|
27
54
|
_say(FAIL, str(e))
|
|
@@ -31,7 +58,10 @@ def cmd_setup(args) -> int:
|
|
|
31
58
|
if config.FAKE_EMBEDDER:
|
|
32
59
|
_say(OK, "Embedding model: test mode, nothing to download")
|
|
33
60
|
else:
|
|
34
|
-
|
|
61
|
+
if any(config.MODELS_DIR.glob("*")):
|
|
62
|
+
print("[ .... ] Loading the embedding model...", flush=True)
|
|
63
|
+
else:
|
|
64
|
+
print("[ .... ] Downloading the embedding model (about 90 MB, only the first time)...", flush=True)
|
|
35
65
|
try:
|
|
36
66
|
from .server import embed
|
|
37
67
|
embed("warm up")
|
|
@@ -121,7 +151,7 @@ def cmd_doctor(args) -> int:
|
|
|
121
151
|
_say(OK, f"Aether Cache {__version__}, Python {sys.version.split()[0]} ({sys.executable})")
|
|
122
152
|
try:
|
|
123
153
|
java, major = engine.find_java()
|
|
124
|
-
_say(OK, f"Java {major} ({java})")
|
|
154
|
+
_say(OK, f"Java {major} ({_java_label(java)})")
|
|
125
155
|
except engine.EngineError as e:
|
|
126
156
|
_say(FAIL, str(e))
|
|
127
157
|
problems += 1
|
|
@@ -329,6 +359,9 @@ def cmd_uninstall(args) -> int:
|
|
|
329
359
|
shutil.rmtree(config.HOME, ignore_errors=True)
|
|
330
360
|
_say(OK, f"Deleted {config.HOME}")
|
|
331
361
|
elif has_data:
|
|
362
|
+
from . import javart
|
|
363
|
+
if javart.remove():
|
|
364
|
+
_say(OK, f"Removed Aether Cache's private copy of Java ({javart.JAVA_DIR})")
|
|
332
365
|
_say(OK, f"Kept your memories in {config.HOME}. Reinstall any time and they're back.")
|
|
333
366
|
print(f"\nLast step, remove the program itself:\n {_uninstall_command()}")
|
|
334
367
|
return 1 if failed else 0
|
|
@@ -34,30 +34,44 @@ def java_install_hint() -> str:
|
|
|
34
34
|
how = "winget install EclipseAdoptium.Temurin.21.JRE (or download it from https://adoptium.net)"
|
|
35
35
|
else:
|
|
36
36
|
how = "sudo apt install openjdk-21-jre-headless (or your distro's equivalent, or https://adoptium.net)"
|
|
37
|
-
return f"
|
|
37
|
+
return (f"Run `aether-cache setup` and it downloads a private copy of Java for you, "
|
|
38
|
+
f"or install Java {MIN_JAVA}+ yourself:\n {how}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _java_version(java: str) -> Optional[int]:
|
|
42
|
+
try:
|
|
43
|
+
out = subprocess.run([java, "-version"], capture_output=True, text=True, timeout=15)
|
|
44
|
+
except (OSError, subprocess.SubprocessError):
|
|
45
|
+
return None
|
|
46
|
+
match = re.search(r'version "(\d+)(?:\.(\d+))?', out.stderr + out.stdout)
|
|
47
|
+
if out.returncode != 0 or not match:
|
|
48
|
+
return None
|
|
49
|
+
major = int(match.group(1))
|
|
50
|
+
if major == 1 and match.group(2): # old-style "1.8.0" means Java 8
|
|
51
|
+
major = int(match.group(2))
|
|
52
|
+
return major
|
|
38
53
|
|
|
39
54
|
|
|
40
55
|
def find_java() -> Tuple[str, int]:
|
|
41
56
|
"""(path to a working java, its major version). Raises EngineError if there's no usable Java.
|
|
57
|
+
Prefers the computer's own Java; falls back to the private copy `setup` downloads when there isn't one.
|
|
42
58
|
Runs `java -version` rather than trusting the PATH: macOS ships a /usr/bin/java stub that only
|
|
43
59
|
says "Unable to locate a Java Runtime"."""
|
|
60
|
+
from .javart import private_java
|
|
44
61
|
candidates = []
|
|
45
|
-
if os.environ.get("
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
if not os.environ.get("AETHER_IGNORE_SYSTEM_JAVA"): # testing only: act as if this computer had no Java
|
|
63
|
+
if os.environ.get("JAVA_HOME"):
|
|
64
|
+
candidates.append(str(Path(os.environ["JAVA_HOME"]) / "bin" / ("java.exe" if os.name == "nt" else "java")))
|
|
65
|
+
if shutil.which("java"):
|
|
66
|
+
candidates.append(shutil.which("java"))
|
|
67
|
+
private = private_java()
|
|
68
|
+
if private:
|
|
69
|
+
candidates.append(str(private))
|
|
49
70
|
too_old = None
|
|
50
71
|
for java in candidates:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
except (OSError, subprocess.SubprocessError):
|
|
72
|
+
major = _java_version(java)
|
|
73
|
+
if major is None:
|
|
54
74
|
continue
|
|
55
|
-
match = re.search(r'version "(\d+)(?:\.(\d+))?', out.stderr + out.stdout)
|
|
56
|
-
if out.returncode != 0 or not match:
|
|
57
|
-
continue
|
|
58
|
-
major = int(match.group(1))
|
|
59
|
-
if major == 1 and match.group(2): # old-style "1.8.0" means Java 8
|
|
60
|
-
major = int(match.group(2))
|
|
61
75
|
if major >= MIN_JAVA:
|
|
62
76
|
return java, major
|
|
63
77
|
too_old = major
|
|
@@ -66,6 +80,14 @@ def find_java() -> Tuple[str, int]:
|
|
|
66
80
|
raise EngineError("Java isn't installed.\n" + java_install_hint())
|
|
67
81
|
|
|
68
82
|
|
|
83
|
+
def is_private(java: str) -> bool:
|
|
84
|
+
from .javart import JAVA_DIR
|
|
85
|
+
try:
|
|
86
|
+
return Path(java).resolve().is_relative_to(JAVA_DIR.resolve())
|
|
87
|
+
except (OSError, ValueError):
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
|
|
69
91
|
# ---------- talking to the engine ----------
|
|
70
92
|
|
|
71
93
|
def client(timeout: Optional[float] = None) -> redis.Redis:
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A private copy of Java, for people who don't have it.
|
|
3
|
+
|
|
4
|
+
The engine needs Java 11 or newer. If this computer has one, it's used and nothing is downloaded. If not,
|
|
5
|
+
`aether-cache setup` downloads Eclipse Temurin (the standard free build of Java) into ~/.aether-cache/java,
|
|
6
|
+
checks the download against the checksum its publisher lists, and uses it only for Aether Cache: nothing is
|
|
7
|
+
installed system-wide, and `aether-cache uninstall` removes it.
|
|
8
|
+
"""
|
|
9
|
+
import hashlib
|
|
10
|
+
import os
|
|
11
|
+
import platform
|
|
12
|
+
import shutil
|
|
13
|
+
import sys
|
|
14
|
+
import tarfile
|
|
15
|
+
import tempfile
|
|
16
|
+
import time
|
|
17
|
+
import zipfile
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Callable, Optional
|
|
20
|
+
|
|
21
|
+
from . import config
|
|
22
|
+
|
|
23
|
+
FEATURE_VERSION = 21 # a long-term-support release
|
|
24
|
+
API = os.environ.get("AETHER_JAVA_API", "https://api.adoptium.net/v3")
|
|
25
|
+
JAVA_DIR = config.HOME / "java"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class JavaDownloadError(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def private_java() -> Optional[Path]:
|
|
33
|
+
"""The java program inside our private copy, if it has been downloaded."""
|
|
34
|
+
name = "java.exe" if os.name == "nt" else "java"
|
|
35
|
+
for pattern in (f"*/Contents/Home/bin/{name}", f"*/bin/{name}"): # macOS keeps it inside a bundle
|
|
36
|
+
for p in sorted(JAVA_DIR.glob(pattern)):
|
|
37
|
+
if p.is_file():
|
|
38
|
+
return p
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _target() -> tuple:
|
|
43
|
+
"""(os, architecture) in the names the download service uses."""
|
|
44
|
+
system = platform.system()
|
|
45
|
+
machine = platform.machine().lower()
|
|
46
|
+
os_name = {"Darwin": "mac", "Windows": "windows", "Linux": "linux"}.get(system)
|
|
47
|
+
if os_name == "linux" and Path("/etc/alpine-release").exists():
|
|
48
|
+
os_name = "alpine-linux"
|
|
49
|
+
arch = {"x86_64": "x64", "amd64": "x64", "arm64": "aarch64", "aarch64": "aarch64"}.get(machine)
|
|
50
|
+
if not os_name or not arch:
|
|
51
|
+
raise JavaDownloadError(f"There's no ready-made Java download for this computer ({system} {machine}).")
|
|
52
|
+
return os_name, arch
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _client():
|
|
56
|
+
import httpx # installed with the MCP library; respects HTTPS_PROXY like the model download does
|
|
57
|
+
return httpx.Client(follow_redirects=True, timeout=httpx.Timeout(30.0, read=60.0))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _find_package(client) -> dict:
|
|
61
|
+
os_name, arch = _target()
|
|
62
|
+
url = f"{API}/assets/latest/{FEATURE_VERSION}/hotspot"
|
|
63
|
+
r = client.get(url, params={"architecture": arch, "image_type": "jre", "os": os_name, "vendor": "eclipse"})
|
|
64
|
+
r.raise_for_status()
|
|
65
|
+
try:
|
|
66
|
+
package = r.json()[0]["binary"]["package"]
|
|
67
|
+
return {"link": package["link"], "sha256": package["checksum"].lower(),
|
|
68
|
+
"size": int(package.get("size") or 0), "name": package["name"]}
|
|
69
|
+
except (ValueError, LookupError, TypeError, AttributeError):
|
|
70
|
+
raise JavaDownloadError(f"The Java download service sent an unexpected answer for {os_name} {arch}.")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _download(client, pkg: dict, dest: Path, progress: Callable[[int, int], None]) -> None:
|
|
74
|
+
digest = hashlib.sha256()
|
|
75
|
+
done = 0
|
|
76
|
+
with client.stream("GET", pkg["link"]) as r:
|
|
77
|
+
r.raise_for_status()
|
|
78
|
+
total = pkg["size"] or int(r.headers.get("content-length") or 0)
|
|
79
|
+
with open(dest, "wb") as f:
|
|
80
|
+
for chunk in r.iter_bytes(1 << 16):
|
|
81
|
+
f.write(chunk)
|
|
82
|
+
digest.update(chunk)
|
|
83
|
+
done += len(chunk)
|
|
84
|
+
progress(done, total)
|
|
85
|
+
if digest.hexdigest() != pkg["sha256"]:
|
|
86
|
+
raise JavaDownloadError("The Java download didn't match its published checksum, so it wasn't used. "
|
|
87
|
+
"Try again; if it keeps happening, install Java yourself from https://adoptium.net.")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _extract(archive: Path, into: Path) -> None:
|
|
91
|
+
if archive.name.endswith(".zip"):
|
|
92
|
+
with zipfile.ZipFile(archive) as z:
|
|
93
|
+
for member in z.namelist(): # refuse paths that would land outside the folder
|
|
94
|
+
if member.startswith(("/", "\\")) or ".." in Path(member).parts:
|
|
95
|
+
raise JavaDownloadError("The Java download contained an unsafe file path.")
|
|
96
|
+
z.extractall(into)
|
|
97
|
+
else:
|
|
98
|
+
with tarfile.open(archive) as t:
|
|
99
|
+
if hasattr(tarfile, "data_filter"):
|
|
100
|
+
t.extractall(into, filter="data") # rejects unsafe paths and links
|
|
101
|
+
else:
|
|
102
|
+
for member in t.getmembers():
|
|
103
|
+
if member.name.startswith("/") or ".." in Path(member.name).parts:
|
|
104
|
+
raise JavaDownloadError("The Java download contained an unsafe file path.")
|
|
105
|
+
t.extractall(into)
|
|
106
|
+
if os.name != "nt": # make sure the programs are runnable (zip files drop permissions)
|
|
107
|
+
for bin_dir in into.glob("*/**/bin"):
|
|
108
|
+
for f in bin_dir.iterdir():
|
|
109
|
+
if f.is_file():
|
|
110
|
+
f.chmod(f.stat().st_mode | 0o755)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
_last_shown = [-1]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _bar(done: int, total: int) -> None:
|
|
117
|
+
if not sys.stdout.isatty():
|
|
118
|
+
return
|
|
119
|
+
step = int(done * 100 / total) if total else done >> 20 # redraw once per percent (or per MB)
|
|
120
|
+
if step == _last_shown[0]:
|
|
121
|
+
return
|
|
122
|
+
_last_shown[0] = step
|
|
123
|
+
mb = done / 1_048_576
|
|
124
|
+
if total:
|
|
125
|
+
width = 24
|
|
126
|
+
filled = int(width * done / total)
|
|
127
|
+
line = f" [{'#' * filled}{'.' * (width - filled)}] {mb:5.1f} of {total / 1_048_576:.0f} MB"
|
|
128
|
+
else:
|
|
129
|
+
line = f" {mb:5.1f} MB"
|
|
130
|
+
print("\r" + line, end="", flush=True)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def download(progress: Callable[[int, int], None] = _bar, attempts: int = 3) -> Path:
|
|
134
|
+
"""Downloads and unpacks Java into ~/.aether-cache/java. Returns the path to its java program."""
|
|
135
|
+
config.HOME.mkdir(parents=True, exist_ok=True)
|
|
136
|
+
last_error = None
|
|
137
|
+
for attempt in range(1, attempts + 1):
|
|
138
|
+
work = Path(tempfile.mkdtemp(prefix="java-download-", dir=config.HOME))
|
|
139
|
+
try:
|
|
140
|
+
with _client() as client:
|
|
141
|
+
pkg = _find_package(client)
|
|
142
|
+
archive = work / pkg["name"]
|
|
143
|
+
_download(client, pkg, archive, progress)
|
|
144
|
+
if progress is _bar and sys.stdout.isatty():
|
|
145
|
+
print()
|
|
146
|
+
unpacked = work / "unpacked"
|
|
147
|
+
unpacked.mkdir()
|
|
148
|
+
_extract(archive, unpacked)
|
|
149
|
+
if JAVA_DIR.exists():
|
|
150
|
+
shutil.rmtree(JAVA_DIR)
|
|
151
|
+
os.replace(unpacked, JAVA_DIR) # appears all at once, so a half-unpacked Java is never used
|
|
152
|
+
java = private_java()
|
|
153
|
+
if java is None:
|
|
154
|
+
raise JavaDownloadError("The Java download didn't contain a java program.")
|
|
155
|
+
return java
|
|
156
|
+
except JavaDownloadError:
|
|
157
|
+
raise # a bad checksum or a bad archive won't fix itself by retrying
|
|
158
|
+
except Exception as e: # network trouble: try again
|
|
159
|
+
last_error = e
|
|
160
|
+
if progress is _bar and sys.stdout.isatty():
|
|
161
|
+
print()
|
|
162
|
+
if attempt < attempts:
|
|
163
|
+
time.sleep(2 * attempt)
|
|
164
|
+
finally:
|
|
165
|
+
shutil.rmtree(work, ignore_errors=True)
|
|
166
|
+
raise JavaDownloadError(f"Couldn't download Java ({last_error}). Check your internet connection and run "
|
|
167
|
+
f"`aether-cache setup` again, or install Java yourself from https://adoptium.net.")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def remove() -> bool:
|
|
171
|
+
if JAVA_DIR.exists():
|
|
172
|
+
shutil.rmtree(JAVA_DIR, ignore_errors=True)
|
|
173
|
+
return True
|
|
174
|
+
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/fonts/ibm-plex-mono-latin-400-normal.woff2
RENAMED
|
File without changes
|
{aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/fonts/ibm-plex-mono-latin-500-normal.woff2
RENAMED
|
File without changes
|
{aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/fonts/ibm-plex-sans-latin-400-normal.woff2
RENAMED
|
File without changes
|
{aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/fonts/ibm-plex-sans-latin-500-normal.woff2
RENAMED
|
File without changes
|
{aether_cache-0.1.3 → aether_cache-0.1.4}/aether_cache/ui/fonts/ibm-plex-sans-latin-600-normal.woff2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|