Skip to content

Commit c270b3a

Browse files
committed
[FEATURE] Backport RemoteProfilesProvider and RemoteUpdatesProvider
1 parent d8e1f28 commit c270b3a

6 files changed

Lines changed: 404 additions & 0 deletions

File tree

components/launcher-core/src/main/java/pro/gravit/launcher/core/hasher/HashedDir.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ public Diff compare(HashedDir other, FileNameMatcher matcher) {
5757
return new Diff(mismatch, extra);
5858
}
5959

60+
public void put(String name, HashedEntry e) {
61+
map.put(name, e);
62+
}
63+
6064
public void remove(String name) {
6165
map.remove(name);
6266
}
@@ -66,6 +70,36 @@ public void moveTo(String elementName, HashedDir target, String targetElementNam
6670
target.map.put(targetElementName, entry);
6771
}
6872

73+
public FindRecursiveResult createParentDirectories(String path) {
74+
StringTokenizer t = new StringTokenizer(path, "/");
75+
HashedDir current = this;
76+
HashedEntry entry = null;
77+
String name = null;
78+
while (t.hasMoreTokens()) {
79+
name = t.nextToken();
80+
HashedEntry e = current.map.get(name);
81+
if (e == null && !t.hasMoreTokens()) {
82+
break;
83+
}
84+
if (e == null) {
85+
e = new HashedDir();
86+
current.map.put(name, e);
87+
}
88+
if (e.getType() == Type.DIR) {
89+
if (!t.hasMoreTokens()) {
90+
entry = e;
91+
break;
92+
} else {
93+
current = ((HashedDir) e);
94+
}
95+
} else {
96+
entry = e;
97+
break;
98+
}
99+
}
100+
return new FindRecursiveResult(current, entry, name);
101+
}
102+
69103
public FindRecursiveResult findRecursive(String path) {
70104
StringTokenizer t = new StringTokenizer(path, "/");
71105
HashedDir current = this;

components/launcher-core/src/main/java/pro/gravit/launcher/core/hasher/HashedFile.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ public HashedFile(Path file, long size, boolean digest) throws IOException {
4646
this(size, digest ? SecurityHelper.digest(DIGEST_ALGO, file) : null);
4747
}
4848

49+
public HashedFile(byte[] bytes) {
50+
this(bytes.length, SecurityHelper.digest(DIGEST_ALGO, bytes));
51+
}
52+
53+
public HashedFile(byte[] bytes, String url) {
54+
this(bytes.length, SecurityHelper.digest(DIGEST_ALGO, bytes), url);
55+
}
56+
4957
@Override
5058
public Type getType() {
5159
return Type.FILE;

components/launchserver/src/main/java/pro/gravit/launchserver/auth/profiles/ProfilesProvider.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public abstract class ProfilesProvider {
2222
public static void registerProviders() {
2323
if (!registredProviders) {
2424
providers.register("local", LocalProfilesProvider.class);
25+
providers.register("remote", RemoteProfilesProvider.class);
2526
registredProviders = true;
2627
}
2728
}
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
package pro.gravit.launchserver.auth.profiles;
2+
3+
import pro.gravit.launcher.base.HttpHelper;
4+
import pro.gravit.launcher.base.profiles.ClientProfile;
5+
import pro.gravit.launcher.base.request.RequestFeatureHttpAPIImpl;
6+
import pro.gravit.launcher.core.hasher.HashedDir;
7+
import pro.gravit.launcher.core.hasher.HashedEntry;
8+
import pro.gravit.launcher.core.hasher.HashedFile;
9+
import pro.gravit.launchserver.socket.Client;
10+
import pro.gravit.utils.helper.IOHelper;
11+
12+
import java.io.ByteArrayOutputStream;
13+
import java.io.IOException;
14+
import java.net.URI;
15+
import java.net.http.HttpClient;
16+
import java.net.http.HttpRequest;
17+
import java.nio.file.Files;
18+
import java.nio.file.Path;
19+
import java.util.*;
20+
import java.util.concurrent.ExecutionException;
21+
import java.util.stream.Collectors;
22+
23+
public class RemoteProfilesProvider extends ProfilesProvider {
24+
public String baseUrl = "PASTE BASE URL HERE";
25+
public String accessToken = "PASTE ACCESS TOKEN HERE";
26+
private final transient HttpClient client = HttpClient.newBuilder().build();
27+
@Override
28+
public UncompletedProfile create(String name, String description, CompletedProfile basic) {
29+
try {
30+
return HttpHelper.sendAsync(client, HttpRequest.newBuilder()
31+
.POST(HttpHelper.jsonBodyPublisher(new HttpCreateProfileRequest(name, description, basic.getProfile())))
32+
.uri(URI.create(baseUrl.concat("/profile/new")))
33+
.header("Authorization", "Bearer "+accessToken)
34+
.build(), new RequestFeatureHttpAPIImpl.HttpErrorHandler<>(HttpUncompletedProfile.class))
35+
.thenApply(HttpHelper.HttpOptional::getOrThrow).get();
36+
} catch (InterruptedException | ExecutionException e) {
37+
throw new RuntimeException(e);
38+
}
39+
}
40+
41+
@Override
42+
public void delete(UncompletedProfile profile) {
43+
try {
44+
HttpHelper.sendAsync(client, HttpRequest.newBuilder()
45+
.DELETE()
46+
.uri(URI.create(baseUrl.concat("/profile/"+profile.getUuid())))
47+
.header("Authorization", "Bearer "+accessToken)
48+
.build(), new RequestFeatureHttpAPIImpl.HttpErrorHandler<>(Void.class))
49+
.thenApply(HttpHelper.HttpOptional::getOrThrow).get();
50+
} catch (InterruptedException | ExecutionException e) {
51+
throw new RuntimeException(e);
52+
}
53+
}
54+
55+
@Override
56+
public Set<UncompletedProfile> getProfiles(Client socketClient) {
57+
try {
58+
return HttpHelper.sendAsync(client, HttpRequest.newBuilder()
59+
.GET()
60+
.uri(URI.create(baseUrl.concat("/profile/list")))
61+
.header("Authorization", "Bearer "+accessToken)
62+
.build(), new RequestFeatureHttpAPIImpl.HttpErrorHandler<>(RequestFeatureHttpAPIImpl.HttpListProfilesResponse.class))
63+
.thenApply(e -> e.getOrThrow().profiles().stream()
64+
.map(HttpUncompletedProfile::new)
65+
.map(x -> (UncompletedProfile) x)
66+
.collect(Collectors.toSet()))
67+
.get();
68+
} catch (InterruptedException | ExecutionException e) {
69+
throw new RuntimeException(e);
70+
}
71+
}
72+
73+
@Override
74+
public CompletedProfile pushUpdate(UncompletedProfile profile, String tag, ClientProfile clientProfile, List<ProfileAction> assetActions, List<ProfileAction> clientActions, List<UpdateFlag> flags) throws IOException {
75+
var prev = get(profile.getUuid(), tag);
76+
HashedDir clientDir = prev.getClientDir();
77+
HashedDir assetDir = prev.getAssetDir();
78+
if(flags.contains(UpdateFlag.USE_DEFAULT_ASSETS)) {
79+
assetDir = getUnconnectedDirectory("assets");
80+
}
81+
if(assetActions != null) {
82+
execute(assetDir, assetActions);
83+
}
84+
if(clientActions != null) {
85+
execute(clientDir, clientActions);
86+
}
87+
88+
try {
89+
return HttpHelper.sendAsync(client, HttpRequest.newBuilder()
90+
.POST(HttpHelper.jsonBodyPublisher(new HttpUpdateProfileRequest(clientProfile, clientDir, assetDir)))
91+
.uri(URI.create(baseUrl.concat("/profile/by/uuid/"+profile.getUuid()+"/pushupdate")))
92+
.header("Authorization", "Bearer "+accessToken)
93+
.build(), new RequestFeatureHttpAPIImpl.HttpErrorHandler<>(HttpProfile.class))
94+
.thenApply(HttpHelper.HttpOptional::getOrThrow).get();
95+
} catch (InterruptedException | ExecutionException e) {
96+
throw new RuntimeException(e);
97+
}
98+
}
99+
100+
@Override
101+
public void download(CompletedProfile profile, Map<String, Path> files, boolean assets) throws IOException {
102+
throw new UnsupportedOperationException();
103+
}
104+
105+
@Override
106+
public HashedDir getUnconnectedDirectory(String name) {
107+
try {
108+
return HttpHelper.sendAsync(client, HttpRequest.newBuilder()
109+
.GET()
110+
.uri(URI.create(baseUrl.concat("/profile/unconnected/"+name)))
111+
.header("Authorization", "Bearer "+accessToken)
112+
.build(), new RequestFeatureHttpAPIImpl.HttpErrorHandler<>(HashedDir.class))
113+
.thenApply(HttpHelper.HttpOptional::getOrThrow)
114+
.get();
115+
} catch (InterruptedException | ExecutionException e) {
116+
throw new RuntimeException(e);
117+
}
118+
}
119+
120+
@Override
121+
public CompletedProfile get(UUID uuid, String tag) {
122+
try {
123+
return HttpHelper.sendAsync(client, HttpRequest.newBuilder()
124+
.GET()
125+
.uri(URI.create(baseUrl.concat("/profile/by/uuid/"+uuid)))
126+
.header("Authorization", "Bearer "+accessToken)
127+
.build(), new RequestFeatureHttpAPIImpl.HttpErrorHandler<>(HttpProfile.class))
128+
.thenApply(HttpHelper.HttpOptional::getOrThrow)
129+
.get();
130+
} catch (InterruptedException | ExecutionException e) {
131+
throw new RuntimeException(e);
132+
}
133+
}
134+
135+
@Override
136+
public CompletedProfile get(String name, String tag) {
137+
try {
138+
return HttpHelper.sendAsync(client, HttpRequest.newBuilder()
139+
.GET()
140+
.uri(URI.create(baseUrl.concat("/profile/by/name/"+name)))
141+
.header("Authorization", "Bearer "+accessToken)
142+
.build(), new RequestFeatureHttpAPIImpl.HttpErrorHandler<>(HttpProfile.class))
143+
.thenApply(HttpHelper.HttpOptional::getOrThrow)
144+
.get();
145+
} catch (InterruptedException | ExecutionException e) {
146+
throw new RuntimeException(e);
147+
}
148+
}
149+
150+
public HttpFileUploadResponse uploadFile(HttpRequest.BodyPublisher bodyPublisher) {
151+
try {
152+
return HttpHelper.sendAsync(client, HttpRequest.newBuilder()
153+
.POST(bodyPublisher)
154+
.uri(URI.create(baseUrl.concat("/profile/uploadfile")))
155+
.header("Authorization", "Bearer "+accessToken)
156+
.build(), new RequestFeatureHttpAPIImpl.HttpErrorHandler<>(HttpFileUploadResponse.class))
157+
.thenApply(HttpHelper.HttpOptional::getOrThrow).get();
158+
} catch (InterruptedException | ExecutionException e) {
159+
throw new RuntimeException(e);
160+
}
161+
}
162+
163+
public void execute(HashedDir dir, List<ProfileAction> actions) throws IOException {
164+
for(var action : actions) {
165+
execute(dir, action);
166+
}
167+
}
168+
169+
public void execute(HashedDir dir, ProfileAction action) throws IOException {
170+
switch (action.type()) {
171+
case UPLOAD -> {
172+
if(action.source() == null) {
173+
var r = dir.createParentDirectories(action.target());
174+
try(var output = new ByteArrayOutputStream()) {
175+
try(var input = action.input().get()) {
176+
input.transferTo(output);
177+
}
178+
byte[] bytes = output.toByteArray();
179+
var res = uploadFile(HttpRequest.BodyPublishers.ofByteArray(bytes));
180+
HashedFile file = new HashedFile(bytes, res.url());
181+
r.parent.put(r.name, file);
182+
}
183+
} else {
184+
HashedDir srcDir = new HashedDir(Path.of(action.source()), null, true, true);
185+
HashedDir.Diff diff = dir.diff(srcDir, null);
186+
diff.mismatch.walk("/", (HashedDir.WalkCallback) (path, name, entry) -> {
187+
if(entry.getType() == HashedEntry.Type.FILE) {
188+
var r = dir.createParentDirectories(action.target());
189+
try(var output = new ByteArrayOutputStream()) {
190+
try(var input = IOHelper.newInput(Path.of(action.source()).resolve(path))) {
191+
input.transferTo(output);
192+
}
193+
byte[] bytes = output.toByteArray();
194+
var res = uploadFile(HttpRequest.BodyPublishers.ofByteArray(bytes));
195+
HashedFile file = new HashedFile(bytes, res.url());
196+
r.parent.put(r.name, file);
197+
} catch (IOException e) {
198+
throw new RuntimeException(e);
199+
}
200+
}
201+
return HashedDir.WalkAction.CONTINUE;
202+
});
203+
}
204+
}
205+
case COPY -> {
206+
throw new UnsupportedOperationException();
207+
}
208+
case MOVE -> {
209+
throw new UnsupportedOperationException();
210+
}
211+
case DELETE -> {
212+
throw new UnsupportedOperationException();
213+
}
214+
}
215+
}
216+
217+
public record HttpFileUploadResponse(String url) {
218+
219+
}
220+
221+
public record HttpCreateProfileRequest(String name, String description, ClientProfile profile) {
222+
223+
}
224+
225+
public record HttpUpdateProfileRequest(ClientProfile profile, HashedDir clientDir, HashedDir assetDir) {
226+
227+
}
228+
229+
public record HttpUncompletedProfile(ClientProfile profile) implements UncompletedProfile {
230+
231+
@Override
232+
public UUID getUuid() {
233+
return profile.getUUID();
234+
}
235+
236+
@Override
237+
public String getName() {
238+
return profile.getName();
239+
}
240+
241+
@Override
242+
public String getDescription() {
243+
return profile.getDescription();
244+
}
245+
246+
@Override
247+
public String getDefaultTag() {
248+
return "";
249+
}
250+
}
251+
252+
public record HttpProfile(ClientProfile profile, HashedDir clientDir, HashedDir assetDir) implements CompletedProfile {
253+
254+
@Override
255+
public String getTag() {
256+
return "";
257+
}
258+
259+
@Override
260+
public ClientProfile getProfile() {
261+
return profile;
262+
}
263+
264+
@Override
265+
public HashedDir getClientDir() {
266+
return clientDir;
267+
}
268+
269+
@Override
270+
public HashedDir getAssetDir() {
271+
return assetDir;
272+
}
273+
274+
@Override
275+
public UUID getUuid() {
276+
return profile.getUUID();
277+
}
278+
279+
@Override
280+
public String getName() {
281+
return profile.getName();
282+
}
283+
284+
@Override
285+
public String getDescription() {
286+
return profile.getDescription();
287+
}
288+
289+
@Override
290+
public String getDefaultTag() {
291+
return "";
292+
}
293+
}
294+
}

0 commit comments

Comments
 (0)