1919
2020package org .maxgamer .quickshop .localization .text .distributions .crowdin ;
2121
22- import com .google .common .cache .Cache ;
23- import com .google .common .cache .CacheBuilder ;
2422import com .google .gson .JsonElement ;
2523import com .google .gson .JsonParser ;
2624import 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 ;
3029import org .apache .commons .codec .digest .DigestUtils ;
3130import org .bukkit .configuration .file .YamlConfiguration ;
3231import org .jetbrains .annotations .NotNull ;
3635import org .maxgamer .quickshop .localization .text .distributions .crowdin .bean .Manifest ;
3736import org .maxgamer .quickshop .util .HttpUtil ;
3837import org .maxgamer .quickshop .util .JsonUtil ;
38+ import org .maxgamer .quickshop .util .MsgUtil ;
3939import org .maxgamer .quickshop .util .Util ;
4040
4141import java .io .File ;
4242import java .io .IOException ;
4343import java .nio .charset .StandardCharsets ;
4444import java .nio .file .Files ;
45- import java .nio .file .StandardOpenOption ;
4645import java .util .*;
47- import java .util .concurrent .TimeUnit ;
48- import java .util .logging .Level ;
46+ import java .util .concurrent .locks .ReentrantLock ;
4947
5048public 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}
0 commit comments