Skip to content

Commit e761161

Browse files
authored
Merge pull request Ghost-chu#1543 from Ghost-chu/master
5.0.0.4
2 parents 0d39c45 + ce22e99 commit e761161

File tree

6 files changed

+200
-98
lines changed

6 files changed

+200
-98
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<artifactId>QuickShop</artifactId>
2626

2727
<properties>
28-
<pluginver>5.0.0.3</pluginver>
28+
<pluginver>5.0.0.4</pluginver>
2929
<package>org.maxgamer.quickshop</package>
3030
<developer>Ghost-chu</developer>
3131
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

src/main/java/org/maxgamer/quickshop/api/shop/Shop.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,7 @@ default List<ComponentPackage> getSignText(String locale) {
562562
default boolean isShopSign(@NotNull Sign sign) {
563563
// Check for new shop sign
564564
String[] lines = sign.getLines();
565+
// Can be claim
565566
if (lines[0].isEmpty() && lines[1].isEmpty() && lines[2].isEmpty() && lines[3].isEmpty()) {
566567
return true;
567568
}

src/main/java/org/maxgamer/quickshop/localization/text/distributions/crowdin/CrowdinOTA.java

Lines changed: 150 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,13 @@
1919

2020
package org.maxgamer.quickshop.localization.text.distributions.crowdin;
2121

22-
import com.google.common.cache.Cache;
23-
import com.google.common.cache.CacheBuilder;
2422
import com.google.gson.JsonElement;
2523
import com.google.gson.JsonParser;
2624
import com.google.gson.JsonPrimitive;
27-
import lombok.*;
28-
import okhttp3.Request;
29-
import okhttp3.Response;
25+
import lombok.AllArgsConstructor;
26+
import lombok.Builder;
27+
import lombok.Data;
28+
import lombok.EqualsAndHashCode;
3029
import org.apache.commons.codec.digest.DigestUtils;
3130
import org.bukkit.configuration.file.YamlConfiguration;
3231
import org.jetbrains.annotations.NotNull;
@@ -36,23 +35,20 @@
3635
import org.maxgamer.quickshop.localization.text.distributions.crowdin.bean.Manifest;
3736
import org.maxgamer.quickshop.util.HttpUtil;
3837
import org.maxgamer.quickshop.util.JsonUtil;
38+
import org.maxgamer.quickshop.util.MsgUtil;
3939
import org.maxgamer.quickshop.util.Util;
4040

4141
import java.io.File;
4242
import java.io.IOException;
4343
import java.nio.charset.StandardCharsets;
4444
import java.nio.file.Files;
45-
import java.nio.file.StandardOpenOption;
4645
import java.util.*;
47-
import java.util.concurrent.TimeUnit;
48-
import java.util.logging.Level;
46+
import java.util.concurrent.locks.ReentrantLock;
4947

5048
public class CrowdinOTA implements Distribution {
5149
protected static final String CROWDIN_OTA_HOST = "https://distributions.crowdin.net/daf1a8db40f132ce157c457xrm4/";
52-
protected final Cache<String, String> requestCachePool = CacheBuilder.newBuilder()
53-
.expireAfterWrite(7, TimeUnit.DAYS)
54-
.build();
5550
private final QuickShop plugin;
51+
private final OTACacheControl otaCacheControl = new OTACacheControl();
5652

5753
public CrowdinOTA(QuickShop plugin) {
5854
this.plugin = plugin;
@@ -68,7 +64,7 @@ public CrowdinOTA(QuickShop plugin) {
6864

6965
@Nullable
7066
public Manifest getManifest() {
71-
return JsonUtil.getGson().fromJson(getManifestJson(), Manifest.class);
67+
return JsonUtil.regular().fromJson(getManifestJson(), Manifest.class);
7268
}
7369

7470
/**
@@ -79,27 +75,7 @@ public Manifest getManifest() {
7975
@Nullable
8076
public String getManifestJson() {
8177
String url = CROWDIN_OTA_HOST + "manifest.json";
82-
String data;
83-
if (requestCachePool.getIfPresent(url) != null) {
84-
return requestCachePool.getIfPresent(url);
85-
}
86-
try (Response response = HttpUtil.create().getClient().newCall(new Request.Builder().get().url(url).build()).execute()) {
87-
val body = response.body();
88-
if (body == null) {
89-
return null;
90-
}
91-
data = body.string();
92-
if (response.code() != 200) {
93-
plugin.getLogger().warning("Couldn't get manifest: " + response.code() + ", please report to QuickShop!");
94-
return null;
95-
}
96-
requestCachePool.put(url, data);
97-
} catch (IOException e) {
98-
e.printStackTrace();
99-
plugin.getLogger().log(Level.WARNING, "Failed to download manifest.json, multi-language system won't work");
100-
return null;
101-
}
102-
return data;
78+
return HttpUtil.createGet(url);
10379
}
10480

10581
/**
@@ -173,63 +149,93 @@ public String getFile(String fileCrowdinPath, String crowdinLocale, boolean forc
173149
if (!manifest.getFiles().contains(fileCrowdinPath)) {
174150
throw new IllegalArgumentException("The file " + fileCrowdinPath + " not exists on Crowdin");
175151
}
176-
if (manifest.getCustomLanguages() != null && !manifest.getCustomLanguages().contains(crowdinLocale)) {
177-
throw new IllegalArgumentException("The locale " + crowdinLocale + " not exists on Crowdin");
178-
}
152+
// if (manifest.getCustom_languages() != null && !manifest.getCustom_languages().contains(crowdinLocale)) {
153+
// throw new IllegalArgumentException("The locale " + crowdinLocale + " not exists on Crowdin");
154+
// }
179155
// Post path (replaced with locale code)
180156
String postProcessingPath = fileCrowdinPath.replace("%locale%", crowdinLocale);
181-
// Create path hash to store the file
182-
String pathHash = DigestUtils.sha1Hex(postProcessingPath);
183-
// Reading metadata
184-
File metadataFile = new File(Util.getCacheFolder(), "i18n.metadata");
185-
YamlConfiguration cacheMetadata = YamlConfiguration.loadConfiguration(metadataFile);
186-
// Reading cloud timestamp
187-
long localeTimestamp = cacheMetadata.getLong(pathHash + ".timestamp");
188-
// Reading locale cache
189-
File cachedDataFile = new File(Util.getCacheFolder(), pathHash);
190-
String data = null;
191-
// Getting local cache
192-
if (cachedDataFile.exists()) {
193-
Util.debugLog("Reading data from local cache: " + cachedDataFile.getCanonicalPath());
194-
data = Util.readToString(cachedDataFile);
195-
}
196-
// invalidate cache, flush it
197-
// force flush required OR local cache not exists OR outdated
198-
if (forceFlush || data == null || localeTimestamp != manifest.getTimestamp()) {
199-
String url = CROWDIN_OTA_HOST + "content" + fileCrowdinPath.replace("%locale%", crowdinLocale);
200-
//Util.debugLog("Reading data from remote server: " + url);
201-
plugin.getLogger().info("Downloading translation " + crowdinLocale + " from: " + url);
202-
try (Response response = HttpUtil.create().getClient().newCall(new Request.Builder().get().url(url).build()).execute()) {
203-
val body = response.body();
204-
if (body == null) {
205-
throw new OTAException(response.code(), ""); // Returns empty string (failed to getting content)
206-
}
207-
data = body.string();
208-
if (response.code() != 200) {
209-
throw new OTAException(response.code(), data);
157+
158+
// Validating the manifest
159+
long manifestTimestamp = getManifest().getTimestamp();
160+
if (otaCacheControl.readManifestTimestamp() == getManifest().getTimestamp() && !forceFlush) {
161+
// Use cache
162+
try {
163+
// Check cache outdated
164+
if (!otaCacheControl.isCachedObjectOutdated(postProcessingPath, manifestTimestamp)) {
165+
// Return the caches
166+
Util.debugLog("Use local cache for " + postProcessingPath);
167+
return new String(otaCacheControl.readObjectCache(postProcessingPath), StandardCharsets.UTF_8);
168+
} else {
169+
Util.debugLog("Local cache outdated for " + postProcessingPath);
170+
Util.debugLog("Excepted " + otaCacheControl.readCachedObjectTimestamp(postProcessingPath) + " actual: " + manifestTimestamp);
210171
}
211-
// save to local cache file
212-
Files.write(cachedDataFile.toPath(), data.getBytes(StandardCharsets.UTF_8));
213-
} catch (IOException e) {
214-
plugin.getLogger().log(Level.WARNING, "Failed to download manifest.json, multi-language system may won't work");
215-
e.printStackTrace();
216-
return "";
172+
} catch (Exception exception) {
173+
MsgUtil.debugStackTrace(exception.getStackTrace());
217174
}
218-
// update cache index
219-
cacheMetadata.set(pathHash + ".timestamp", manifest.getTimestamp());
220-
cacheMetadata.save(metadataFile);
221-
return data;
175+
} else {
176+
Util.debugLog("Manifest timestamp check failed " + postProcessingPath + " excepted:" + otaCacheControl.readManifestTimestamp() + " actual: " + getManifest().getTimestamp() + " forceUpdate: " + forceFlush);
177+
}
178+
// Out of the cache
179+
String url = CROWDIN_OTA_HOST + "content" + fileCrowdinPath.replace("%locale%", crowdinLocale);
180+
plugin.getLogger().info("Updating translation " + crowdinLocale + " from: " + url);
181+
String data = HttpUtil.createGet(url);
182+
if (data == null) {
183+
// Failed to grab data
184+
throw new OTAException();
222185
}
186+
// Successfully grab the data from the remote server
187+
otaCacheControl.writeObjectCache(postProcessingPath, data.getBytes(StandardCharsets.UTF_8), manifestTimestamp);
188+
otaCacheControl.writeManifestTimestamp(getManifest().getTimestamp());
223189
return data;
190+
191+
// String pathHash = DigestUtils.sha1Hex(postProcessingPath);
192+
// // Reading metadata
193+
// File metadataFile = new File(Util.getCacheFolder(), "i18n.metadata");
194+
// YamlConfiguration cacheMetadata = YamlConfiguration.loadConfiguration(metadataFile);
195+
// // Reading cloud timestamp
196+
// long localeTimestamp = cacheMetadata.getLong(pathHash + ".timestamp");
197+
// // Reading locale cache
198+
// File cachedDataFile = new File(Util.getCacheFolder(), pathHash);
199+
// String data = null;
200+
// // Getting local cache
201+
// if (cachedDataFile.exists()) {
202+
// Util.debugLog("Reading data from local cache: " + cachedDataFile.getCanonicalPath());
203+
// data = Util.readToString(cachedDataFile);
204+
// }
205+
// // invalidate cache, flush it
206+
// // force flush required OR local cache not exists OR outdated
207+
// if (forceFlush || data == null || localeTimestamp != manifest.getTimestamp()) {
208+
// String url = CROWDIN_OTA_HOST + "content" + fileCrowdinPath.replace("%locale%", crowdinLocale);
209+
// //Util.debugLog("Reading data from remote server: " + url);
210+
// plugin.getLogger().info("Downloading translation " + crowdinLocale + " from: " + url);
211+
// try (Response response = HttpUtil.create().getClient().newCall(new Request.Builder().get().url(url).build()).execute()) {
212+
// val body = response.body();
213+
// if (body == null) {
214+
// throw new OTAException(response.code(), ""); // Returns empty string (failed to getting content)
215+
// }
216+
// data = body.string();
217+
// if (response.code() != 200) {
218+
// throw new OTAException(response.code(), data);
219+
// }
220+
// // save to local cache file
221+
// Files.write(cachedDataFile.toPath(), data.getBytes(StandardCharsets.UTF_8));
222+
// } catch (IOException e) {
223+
// plugin.getLogger().log(Level.WARNING, "Failed to download manifest.json, multi-language system may won't work");
224+
// e.printStackTrace();
225+
// return "";
226+
// }
227+
// // update cache index
228+
// cacheMetadata.set(pathHash + ".timestamp", manifest.getTimestamp());
229+
// cacheMetadata.save(metadataFile);
230+
// return data;
231+
// }
224232
}
225233

226234
@EqualsAndHashCode(callSuper = true)
227235
@AllArgsConstructor
228236
@Builder
229237
@Data
230238
public static class OTAException extends Exception {
231-
private int httpCode;
232-
private String content;
233239
}
234240

235241
@AllArgsConstructor
@@ -240,4 +246,71 @@ public static class CrowdinGetFileRequest {
240246
private String crowdinLocale;
241247
private boolean forceFlush;
242248
}
249+
250+
public static class OTACacheControl {
251+
private final File metadataFile = new File(Util.getCacheFolder(), "i18n.metadata");
252+
private final YamlConfiguration metadata;
253+
private final ReentrantLock LOCK = new ReentrantLock();
254+
255+
public OTACacheControl() {
256+
this.metadata = YamlConfiguration.loadConfiguration(this.metadataFile);
257+
}
258+
259+
private void save() {
260+
LOCK.lock();
261+
try {
262+
this.metadata.save(this.metadataFile);
263+
} catch (Exception exception) {
264+
exception.printStackTrace();
265+
} finally {
266+
LOCK.unlock();
267+
}
268+
}
269+
270+
private String hash(String str) {
271+
return DigestUtils.sha1Hex(str);
272+
}
273+
274+
public long readManifestTimestamp() {
275+
LOCK.lock();
276+
long l = this.metadata.getLong("manifest.timestamp", -1);
277+
LOCK.unlock();
278+
return l;
279+
}
280+
281+
public void writeManifestTimestamp(long timestamp) {
282+
LOCK.lock();
283+
this.metadata.set("manifest.timestamp", timestamp);
284+
LOCK.unlock();
285+
save();
286+
}
287+
288+
public long readCachedObjectTimestamp(String path) {
289+
String cacheKey = hash(path);
290+
LOCK.lock();
291+
long l = this.metadata.getLong("objects." + cacheKey + ".time", -1);
292+
LOCK.unlock();
293+
return l;
294+
}
295+
296+
public boolean isCachedObjectOutdated(String path, long manifestTimestamp) {
297+
return readCachedObjectTimestamp(path) != manifestTimestamp;
298+
}
299+
300+
public byte[] readObjectCache(String path) throws IOException {
301+
String cacheKey = hash(path);
302+
return Files.readAllBytes(new File(Util.getCacheFolder(), cacheKey).toPath());
303+
}
304+
305+
public void writeObjectCache(String path, byte[] data, long manifestTimestamp) throws IOException {
306+
String cacheKey = hash(path);
307+
Files.write(new File(Util.getCacheFolder(), cacheKey).toPath(), data);
308+
LOCK.lock();
309+
this.metadata.set("objects." + cacheKey + ".time", manifestTimestamp);
310+
LOCK.unlock();
311+
save();
312+
}
313+
314+
315+
}
243316
}
Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
1+
/*
2+
* This file is a part of project QuickShop, the name is Manifest.java
3+
* Copyright (C) PotatoCraft Studio and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License as published by the
7+
* Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful, but WITHOUT
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
* for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*
18+
*/
19+
120
package org.maxgamer.quickshop.localization.text.distributions.crowdin.bean;
221

3-
import com.fasterxml.jackson.annotation.JsonProperty;
22+
import lombok.AllArgsConstructor;
423
import lombok.Data;
524
import lombok.NoArgsConstructor;
625

726
import java.util.List;
827

928

10-
@NoArgsConstructor
29+
@AllArgsConstructor
1130
@Data
31+
@NoArgsConstructor
1232
public class Manifest {
13-
14-
@JsonProperty("files")
1533
private List<String> files;
16-
@JsonProperty("languages")
1734
private List<String> languages;
18-
@JsonProperty("custom_languages")
19-
private List<?> customLanguages;
20-
@JsonProperty("timestamp")
21-
private Integer timestamp;
22-
35+
private List<?> custom_languages;
36+
private long timestamp;
2337
}

0 commit comments

Comments
 (0)