2 * Copyright © 2021 Damyan Ivanov.
3 * This file is part of MoLe.
4 * MoLe is free software: you can distribute it and/or modify it
5 * under the term of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your opinion), any later version.
9 * MoLe is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License terms for details.
14 * You should have received a copy of the GNU General Public License
15 * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
18 package net.ktnx.mobileledger.ui.profiles;
20 import android.text.TextUtils;
22 import androidx.lifecycle.LifecycleOwner;
23 import androidx.lifecycle.LiveData;
24 import androidx.lifecycle.MutableLiveData;
25 import androidx.lifecycle.Observer;
26 import androidx.lifecycle.ViewModel;
28 import net.ktnx.mobileledger.App;
29 import net.ktnx.mobileledger.db.Profile;
30 import net.ktnx.mobileledger.json.API;
31 import net.ktnx.mobileledger.model.FutureDates;
32 import net.ktnx.mobileledger.model.HledgerVersion;
33 import net.ktnx.mobileledger.utils.Colors;
34 import net.ktnx.mobileledger.utils.Logger;
35 import net.ktnx.mobileledger.utils.Misc;
36 import net.ktnx.mobileledger.utils.NetworkUtil;
38 import java.io.BufferedReader;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.InputStreamReader;
42 import java.net.HttpURLConnection;
43 import java.util.Locale;
44 import java.util.Objects;
45 import java.util.regex.Matcher;
46 import java.util.regex.Pattern;
48 import static net.ktnx.mobileledger.db.Profile.NO_PROFILE_ID;
50 public class ProfileDetailModel extends ViewModel {
51 private static final String HTTPS_URL_START = "https://";
52 private final MutableLiveData<String> profileName = new MutableLiveData<>();
53 private final MutableLiveData<Boolean> postingPermitted = new MutableLiveData<>(true);
54 private final MutableLiveData<String> defaultCommodity = new MutableLiveData<>(null);
55 private final MutableLiveData<FutureDates> futureDates =
56 new MutableLiveData<>(FutureDates.None);
57 private final MutableLiveData<Boolean> showCommodityByDefault = new MutableLiveData<>(false);
58 private final MutableLiveData<Boolean> showCommentsByDefault = new MutableLiveData<>(true);
59 private final MutableLiveData<Boolean> useAuthentication = new MutableLiveData<>(false);
60 private final MutableLiveData<API> apiVersion = new MutableLiveData<>(API.auto);
61 private final MutableLiveData<String> url = new MutableLiveData<>(null);
62 private final MutableLiveData<String> authUserName = new MutableLiveData<>(null);
63 private final MutableLiveData<String> authPassword = new MutableLiveData<>(null);
64 private final MutableLiveData<String> preferredAccountsFilter = new MutableLiveData<>(null);
65 private final MutableLiveData<Integer> themeId = new MutableLiveData<>(-1);
66 private final MutableLiveData<HledgerVersion> detectedVersion = new MutableLiveData<>(null);
67 private final MutableLiveData<Boolean> detectingHledgerVersion = new MutableLiveData<>(false);
68 private final MutableLiveData<Long> profileId = new MutableLiveData<>(NO_PROFILE_ID);
69 public int initialThemeHue = Colors.DEFAULT_HUE_DEG;
70 private VersionDetectionThread versionDetectionThread;
71 public ProfileDetailModel() {
73 String getProfileName() {
74 return profileName.getValue();
76 void setProfileName(String newValue) {
77 if (!Misc.nullIsEmpty(newValue)
78 .equals(Misc.nullIsEmpty(profileName.getValue())))
79 profileName.setValue(newValue);
81 void setProfileName(CharSequence newValue) {
82 setProfileName(String.valueOf(newValue));
84 void observeProfileName(LifecycleOwner lfo, Observer<String> o) {
85 profileName.observe(lfo, o);
87 Boolean getPostingPermitted() {
88 return postingPermitted.getValue();
90 void setPostingPermitted(boolean newValue) {
91 if (newValue != postingPermitted.getValue())
92 postingPermitted.setValue(newValue);
94 void observePostingPermitted(LifecycleOwner lfo, Observer<Boolean> o) {
95 postingPermitted.observe(lfo, o);
97 public void setShowCommentsByDefault(boolean newValue) {
98 if (newValue != showCommentsByDefault.getValue())
99 showCommentsByDefault.setValue(newValue);
101 void observeShowCommentsByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
102 showCommentsByDefault.observe(lfo, o);
104 FutureDates getFutureDates() {
105 return futureDates.getValue();
107 void setFutureDates(FutureDates newValue) {
108 if (newValue != futureDates.getValue())
109 futureDates.setValue(newValue);
111 void observeFutureDates(LifecycleOwner lfo, Observer<FutureDates> o) {
112 futureDates.observe(lfo, o);
114 String getDefaultCommodity() {
115 return defaultCommodity.getValue();
117 void setDefaultCommodity(String newValue) {
118 if (!Misc.equalStrings(newValue, defaultCommodity.getValue()))
119 defaultCommodity.setValue(newValue);
121 void observeDefaultCommodity(LifecycleOwner lfo, Observer<String> o) {
122 defaultCommodity.observe(lfo, o);
124 Boolean getShowCommodityByDefault() {
125 return showCommodityByDefault.getValue();
127 void setShowCommodityByDefault(boolean newValue) {
128 if (newValue != showCommodityByDefault.getValue())
129 showCommodityByDefault.setValue(newValue);
131 void observeShowCommodityByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
132 showCommodityByDefault.observe(lfo, o);
134 public Boolean getUseAuthentication() {
135 return useAuthentication.getValue();
137 void setUseAuthentication(boolean newValue) {
138 if (newValue != useAuthentication.getValue())
139 useAuthentication.setValue(newValue);
141 void observeUseAuthentication(LifecycleOwner lfo, Observer<Boolean> o) {
142 useAuthentication.observe(lfo, o);
144 API getApiVersion() {
145 return apiVersion.getValue();
147 void setApiVersion(API newValue) {
148 if (newValue != apiVersion.getValue())
149 apiVersion.setValue(newValue);
151 void observeApiVersion(LifecycleOwner lfo, Observer<API> o) {
152 apiVersion.observe(lfo, o);
154 HledgerVersion getDetectedVersion() { return detectedVersion.getValue(); }
155 void setDetectedVersion(HledgerVersion newValue) {
156 if (!Objects.equals(detectedVersion.getValue(), newValue))
157 detectedVersion.setValue(newValue);
159 void observeDetectedVersion(LifecycleOwner lfo, Observer<HledgerVersion> o) {
160 detectedVersion.observe(lfo, o);
162 public String getUrl() {
163 return url.getValue();
165 void setUrl(String newValue) {
166 if (!Misc.nullIsEmpty(newValue)
167 .equals(Misc.nullIsEmpty(url.getValue())))
168 url.setValue(newValue);
170 void setUrl(CharSequence newValue) {
171 setUrl(String.valueOf(newValue));
173 void observeUrl(LifecycleOwner lfo, Observer<String> o) {
176 public String getAuthUserName() {
177 return authUserName.getValue();
179 void setAuthUserName(String newValue) {
180 if (!Misc.nullIsEmpty(newValue)
181 .equals(Misc.nullIsEmpty(authUserName.getValue())))
182 authUserName.setValue(newValue);
184 void setAuthUserName(CharSequence newValue) {
185 setAuthUserName(String.valueOf(newValue));
187 void observeUserName(LifecycleOwner lfo, Observer<String> o) {
188 authUserName.observe(lfo, o);
190 public String getAuthPassword() {
191 return authPassword.getValue();
193 void setAuthPassword(String newValue) {
194 if (!Misc.nullIsEmpty(newValue)
195 .equals(Misc.nullIsEmpty(authPassword.getValue())))
196 authPassword.setValue(newValue);
198 void setAuthPassword(CharSequence newValue) {
199 setAuthPassword(String.valueOf(newValue));
201 void observePassword(LifecycleOwner lfo, Observer<String> o) {
202 authPassword.observe(lfo, o);
204 String getPreferredAccountsFilter() {
205 return preferredAccountsFilter.getValue();
207 void setPreferredAccountsFilter(String newValue) {
208 if (!Misc.nullIsEmpty(newValue)
209 .equals(Misc.nullIsEmpty(preferredAccountsFilter.getValue())))
210 preferredAccountsFilter.setValue(newValue);
212 void setPreferredAccountsFilter(CharSequence newValue) {
213 setPreferredAccountsFilter(String.valueOf(newValue));
215 void observePreferredAccountsFilter(LifecycleOwner lfo, Observer<String> o) {
216 preferredAccountsFilter.observe(lfo, o);
219 return themeId.getValue();
221 void setThemeId(int newValue) {
222 themeId.setValue(newValue);
224 void observeThemeId(LifecycleOwner lfo, Observer<Integer> o) {
225 themeId.observe(lfo, o);
227 void observeDetectingHledgerVersion(LifecycleOwner lfo, Observer<Boolean> o) {
228 detectingHledgerVersion.observe(lfo, o);
230 void setValuesFromProfile(Profile mProfile) {
231 if (mProfile != null) {
232 profileId.setValue(mProfile.getId());
233 profileName.setValue(mProfile.getName());
234 postingPermitted.setValue(mProfile.permitPosting());
235 showCommentsByDefault.setValue(mProfile.getShowCommentsByDefault());
236 showCommodityByDefault.setValue(mProfile.getShowCommodityByDefault());
238 String comm = mProfile.getDefaultCommodity();
239 if (TextUtils.isEmpty(comm))
240 setDefaultCommodity(null);
242 setDefaultCommodity(comm);
244 futureDates.setValue(FutureDates.valueOf(mProfile.getFutureDates()));
245 apiVersion.setValue(API.valueOf(mProfile.getApiVersion()));
246 url.setValue(mProfile.getUrl());
247 useAuthentication.setValue(mProfile.useAuthentication());
248 authUserName.setValue(mProfile.useAuthentication() ? mProfile.getAuthUser() : "");
249 authPassword.setValue(mProfile.useAuthentication() ? mProfile.getAuthPassword() : "");
250 preferredAccountsFilter.setValue(mProfile.getPreferredAccountsFilter());
251 themeId.setValue(mProfile.getTheme());
252 detectedVersion.setValue(mProfile.detectedVersionPre_1_19() ? new HledgerVersion(true)
253 : new HledgerVersion(
254 mProfile.getDetectedVersionMajor(),
255 mProfile.getDetectedVersionMinor()));
258 profileId.setValue(NO_PROFILE_ID);
259 profileName.setValue(null);
260 url.setValue(HTTPS_URL_START);
261 postingPermitted.setValue(true);
262 showCommentsByDefault.setValue(true);
263 showCommodityByDefault.setValue(false);
264 setFutureDates(FutureDates.None);
265 setApiVersion(API.auto);
266 useAuthentication.setValue(false);
267 authUserName.setValue("");
268 authPassword.setValue("");
269 preferredAccountsFilter.setValue(null);
270 detectedVersion.setValue(null);
273 void updateProfile(Profile mProfile) {
274 mProfile.setId(profileId.getValue());
275 mProfile.setName(profileName.getValue());
276 mProfile.setUrl(url.getValue());
277 mProfile.setPermitPosting(postingPermitted.getValue());
278 mProfile.setShowCommentsByDefault(showCommentsByDefault.getValue());
279 mProfile.setDefaultCommodity(defaultCommodity.getValue());
280 mProfile.setShowCommodityByDefault(showCommodityByDefault.getValue());
281 mProfile.setPreferredAccountsFilter(preferredAccountsFilter.getValue());
282 mProfile.setUseAuthentication(useAuthentication.getValue());
283 mProfile.setAuthUser(authUserName.getValue());
284 mProfile.setAuthPassword(authPassword.getValue());
285 mProfile.setTheme(themeId.getValue());
286 mProfile.setFutureDates(futureDates.getValue()
288 mProfile.setApiVersion(apiVersion.getValue()
290 HledgerVersion version = detectedVersion.getValue();
291 mProfile.setDetectedVersionPre_1_19(version != null && version.isPre_1_20_1());
292 mProfile.setDetectedVersionMajor(version != null ? version.getMajor() : -1);
293 mProfile.setDetectedVersionMinor(version != null ? version.getMinor() : -1);
295 synchronized public void triggerVersionDetection() {
296 if (versionDetectionThread != null)
297 versionDetectionThread.interrupt();
299 versionDetectionThread = new VersionDetectionThread(this);
300 versionDetectionThread.start();
302 public LiveData<Long> getProfileId() {
305 static class VersionDetectionThread extends Thread {
306 static final int TARGET_PROCESS_DURATION = 1000;
307 private final Pattern versionPattern =
308 Pattern.compile("^\"(\\d+)\\.(\\d+)(?:\\.(\\d+))?\"$");
309 private final ProfileDetailModel model;
310 public VersionDetectionThread(ProfileDetailModel model) {
313 private HledgerVersion detectVersion() {
314 App.setAuthenticationDataFromProfileModel(model);
315 HttpURLConnection http;
317 http = NetworkUtil.prepareConnection(model.getUrl(), "version",
318 model.getUseAuthentication());
319 switch (http.getResponseCode()) {
323 return new HledgerVersion(true);
325 Logger.debug("profile", String.format(Locale.US,
326 "HTTP error detecting hledger-web version: [%d] %s",
327 http.getResponseCode(), http.getResponseMessage()));
330 InputStream stream = http.getInputStream();
331 BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
332 String version = reader.readLine();
333 Matcher m = versionPattern.matcher(version);
335 int major = Integer.parseInt(Objects.requireNonNull(m.group(1)));
336 int minor = Integer.parseInt(Objects.requireNonNull(m.group(2)));
337 final boolean hasPatch = m.groupCount() >= 3;
338 int patch = hasPatch ? Integer.parseInt(Objects.requireNonNull(m.group(3))) : 0;
340 return hasPatch ? new HledgerVersion(major, minor, patch)
341 : new HledgerVersion(major, minor);
344 Logger.debug("profile",
345 String.format("Unrecognised version string '%s'", version));
349 catch (IOException e) {
354 App.resetAuthenticationData();
359 model.detectingHledgerVersion.postValue(true);
361 long startTime = System.currentTimeMillis();
363 final HledgerVersion version = detectVersion();
365 long elapsed = System.currentTimeMillis() - startTime;
366 Logger.debug("profile", "Detection duration " + elapsed);
367 if (elapsed < TARGET_PROCESS_DURATION) {
369 Thread.sleep(TARGET_PROCESS_DURATION - elapsed);
371 catch (InterruptedException e) {
375 model.detectedVersion.postValue(version);
378 model.detectingHledgerVersion.postValue(false);