content: /*
   * This file is part of LSPosed.
   *
   * LSPosed is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * LSPosed is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with LSPosed.  If not, see <https://www.gnu.org/licenses/>.
   *
   * Copyright (C) 2022 LSPosed Contributors
   */
  
  package org.lsposed.manager.util;
  
  import android.util.Log;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  
  import com.google.gson.JsonObject;
  import com.google.gson.JsonParser;
  
  import org.lsposed.manager.App;
  import org.lsposed.manager.BuildConfig;
  import org.lsposed.manager.ConfigManager;
  
  import java.io.File;
  import java.io.IOException;
  import java.time.Instant;
  import java.time.ZoneOffset;
  import java.util.Locale;
  
  import okhttp3.Call;
  import okhttp3.Callback;
  import okhttp3.Request;
  import okhttp3.Response;
  import okio.Okio;
  
  public class UpdateUtil {
      public static void loadRemoteVersion() {
          var request = new Request.Builder()
                  .url("https://api.github.com/repos/LSPosed/LSPosed/releases/latest")
                  .addHeader("Accept", "application/vnd.github.v3+json")
                  .build();
          var callback = new Callback() {
              @Override
              public void onResponse(@NonNull Call call, @NonNull Response response) {
                  if (!response.isSuccessful()) return;
                  var body = response.body();
                  if (body == null) return;
                  String api = ConfigManager.isBinderAlive() ? ConfigManager.getApi() : "riru";
                  try {
                      var info = JsonParser.parseReader(body.charStream()).getAsJsonObject();
                      var notes = info.get("body").getAsString();
                      var assetsArray = info.getAsJsonArray("assets");
                      for (var assets : assetsArray) {
                          checkAssets(assets.getAsJsonObject(), notes, api.toLowerCase(Locale.ROOT));
                      }
                  } catch (Throwable t) {
                      Log.e(App.TAG, t.getMessage(), t);
                  }
              }
  
              @Override
              public void onFailure(@NonNull Call call, @NonNull IOException e) {
                  Log.e(App.TAG, "loadRemoteVersion: " + e.getMessage());
                  var pref = App.getPreferences();
                  if (pref.getBoolean("checked", false)) return;
                  pref.edit().putBoolean("checked", true).apply();
              }
          };
          App.getOkHttpClient().newCall(request).enqueue(callback);
      }
  
      private static void checkAssets(JsonObject assets, String releaseNotes, String api) {
          var pref = App.getPreferences();
          var name = assets.get("name").getAsString();
          var splitName = name.split("-");
          if (!splitName[3].equals(api)) return;
          pref.edit()
                  .putInt("latest_version", Integer.parseInt(splitName[2]))
                  .putLong("latest_check", Instant.now().getEpochSecond())
                  .putString("release_notes", releaseNotes)
                  .putString("zip_file", null)
                  .putBoolean("checked", true)
                  .apply();
          var updatedAt = Instant.parse(assets.get("updated_at").getAsString());
          var downloadUrl = assets.get("browser_download_url").getAsString();
          var zipTime = pref.getLong("zip_time", 0);
          if (!updatedAt.equals(Instant.ofEpochSecond(zipTime))) {
              var zip = downloadNewZipSync(downloadUrl, name);
              var size = assets.get("size").getAsLong();
              if (zip != null && zip.length() == size) {
                  pref.edit()
                          .putLong("zip_time", updatedAt.getEpochSecond())
                          .putString("zip_file", zip.getAbsolutePath())
                          .apply();
              }
          }
      }
  
      public static boolean needUpdate() {
          var pref = App.getPreferences();
          if (!pref.getBoolean("checked", false)) return false;
          var now = Instant.now();
          var buildTime = Instant.ofEpochSecond(BuildConfig.BUILD_TIME);
          var check = pref.getLong("latest_check", 0);
          if (check > 0) {
              var checkTime = Instant.ofEpochSecond(check);
              if (checkTime.atOffset(ZoneOffset.UTC).plusDays(30).toInstant().isBefore(now))
                  return true;
              var code = pref.getInt("latest_version", 0);
              return code > BuildConfig.VERSION_CODE;
          }
          return buildTime.atOffset(ZoneOffset.UTC).plusDays(30).toInstant().isBefore(now);
      }
  
      @Nullable
      private static File downloadNewZipSync(String url, String name) {
          var request = new Request.Builder().url(url).build();
          var zip = new File(App.getInstance().getCacheDir(), name);
          try (Response response = App.getOkHttpClient().newCall(request).execute()) {
              var body = response.body();
              if (!response.isSuccessful() || body == null) return null;
              try (var source = body.source();
                   var sink = Okio.buffer(Okio.sink(zip))) {
                  sink.writeAll(source);
              }
          } catch (IOException e) {
              Log.e(App.TAG, "downloadNewZipSync: " + e.getMessage());
              return null;
          }
          return zip;
      }
  
      public static boolean canInstall() {
          if (!ConfigManager.isBinderAlive()) return false;
          var pref = App.getPreferences();
          var zip = pref.getString("zip_file", null);
          return zip != null && new File(zip).isFile();
      }
  }
  