]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailModel.java
provide cloud backup functionality
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / profiles / ProfileDetailModel.java
1 /*
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.
8  *
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.
13  *
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/>.
16  */
17
18 package net.ktnx.mobileledger.ui.profiles;
19
20 import android.text.TextUtils;
21
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;
27
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;
37
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;
47
48 import static net.ktnx.mobileledger.db.Profile.NO_PROFILE_ID;
49
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<Integer> orderNo = new MutableLiveData<>();
54     private final MutableLiveData<Boolean> postingPermitted = new MutableLiveData<>(true);
55     private final MutableLiveData<String> defaultCommodity = new MutableLiveData<>(null);
56     private final MutableLiveData<FutureDates> futureDates =
57             new MutableLiveData<>(FutureDates.None);
58     private final MutableLiveData<Boolean> showCommodityByDefault = new MutableLiveData<>(false);
59     private final MutableLiveData<Boolean> showCommentsByDefault = new MutableLiveData<>(true);
60     private final MutableLiveData<Boolean> useAuthentication = new MutableLiveData<>(false);
61     private final MutableLiveData<API> apiVersion = new MutableLiveData<>(API.auto);
62     private final MutableLiveData<String> url = new MutableLiveData<>(null);
63     private final MutableLiveData<String> authUserName = new MutableLiveData<>(null);
64     private final MutableLiveData<String> authPassword = new MutableLiveData<>(null);
65     private final MutableLiveData<String> preferredAccountsFilter = new MutableLiveData<>(null);
66     private final MutableLiveData<Integer> themeId = new MutableLiveData<>(-1);
67     private final MutableLiveData<HledgerVersion> detectedVersion = new MutableLiveData<>(null);
68     private final MutableLiveData<Boolean> detectingHledgerVersion = new MutableLiveData<>(false);
69     private final MutableLiveData<Long> profileId = new MutableLiveData<>(NO_PROFILE_ID);
70     public int initialThemeHue = Colors.DEFAULT_HUE_DEG;
71     private VersionDetectionThread versionDetectionThread;
72     public ProfileDetailModel() {
73     }
74     String getProfileName() {
75         return profileName.getValue();
76     }
77     void setProfileName(String newValue) {
78         if (!Misc.nullIsEmpty(newValue)
79                  .equals(Misc.nullIsEmpty(profileName.getValue())))
80             profileName.setValue(newValue);
81     }
82     void setProfileName(CharSequence newValue) {
83         setProfileName(String.valueOf(newValue));
84     }
85     void observeProfileName(LifecycleOwner lfo, Observer<String> o) {
86         profileName.observe(lfo, o);
87     }
88     Boolean getPostingPermitted() {
89         return postingPermitted.getValue();
90     }
91     void setPostingPermitted(boolean newValue) {
92         if (newValue != postingPermitted.getValue())
93             postingPermitted.setValue(newValue);
94     }
95     void observePostingPermitted(LifecycleOwner lfo, Observer<Boolean> o) {
96         postingPermitted.observe(lfo, o);
97     }
98     public void setShowCommentsByDefault(boolean newValue) {
99         if (newValue != showCommentsByDefault.getValue())
100             showCommentsByDefault.setValue(newValue);
101     }
102     void observeShowCommentsByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
103         showCommentsByDefault.observe(lfo, o);
104     }
105     FutureDates getFutureDates() {
106         return futureDates.getValue();
107     }
108     void setFutureDates(FutureDates newValue) {
109         if (newValue != futureDates.getValue())
110             futureDates.setValue(newValue);
111     }
112     void observeFutureDates(LifecycleOwner lfo, Observer<FutureDates> o) {
113         futureDates.observe(lfo, o);
114     }
115     String getDefaultCommodity() {
116         return defaultCommodity.getValue();
117     }
118     void setDefaultCommodity(String newValue) {
119         if (!Misc.equalStrings(newValue, defaultCommodity.getValue()))
120             defaultCommodity.setValue(newValue);
121     }
122     void observeDefaultCommodity(LifecycleOwner lfo, Observer<String> o) {
123         defaultCommodity.observe(lfo, o);
124     }
125     Boolean getShowCommodityByDefault() {
126         return showCommodityByDefault.getValue();
127     }
128     void setShowCommodityByDefault(boolean newValue) {
129         if (newValue != showCommodityByDefault.getValue())
130             showCommodityByDefault.setValue(newValue);
131     }
132     void observeShowCommodityByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
133         showCommodityByDefault.observe(lfo, o);
134     }
135     public Boolean getUseAuthentication() {
136         return useAuthentication.getValue();
137     }
138     void setUseAuthentication(boolean newValue) {
139         if (newValue != useAuthentication.getValue())
140             useAuthentication.setValue(newValue);
141     }
142     void observeUseAuthentication(LifecycleOwner lfo, Observer<Boolean> o) {
143         useAuthentication.observe(lfo, o);
144     }
145     API getApiVersion() {
146         return apiVersion.getValue();
147     }
148     void setApiVersion(API newValue) {
149         if (newValue != apiVersion.getValue())
150             apiVersion.setValue(newValue);
151     }
152     void observeApiVersion(LifecycleOwner lfo, Observer<API> o) {
153         apiVersion.observe(lfo, o);
154     }
155     HledgerVersion getDetectedVersion() { return detectedVersion.getValue(); }
156     void setDetectedVersion(HledgerVersion newValue) {
157         if (!Objects.equals(detectedVersion.getValue(), newValue))
158             detectedVersion.setValue(newValue);
159     }
160     void observeDetectedVersion(LifecycleOwner lfo, Observer<HledgerVersion> o) {
161         detectedVersion.observe(lfo, o);
162     }
163     public String getUrl() {
164         return url.getValue();
165     }
166     void setUrl(String newValue) {
167         if (!Misc.nullIsEmpty(newValue)
168                  .equals(Misc.nullIsEmpty(url.getValue())))
169             url.setValue(newValue);
170     }
171     void setUrl(CharSequence newValue) {
172         setUrl(String.valueOf(newValue));
173     }
174     void observeUrl(LifecycleOwner lfo, Observer<String> o) {
175         url.observe(lfo, o);
176     }
177     public String getAuthUserName() {
178         return authUserName.getValue();
179     }
180     void setAuthUserName(String newValue) {
181         if (!Misc.nullIsEmpty(newValue)
182                  .equals(Misc.nullIsEmpty(authUserName.getValue())))
183             authUserName.setValue(newValue);
184     }
185     void setAuthUserName(CharSequence newValue) {
186         setAuthUserName(String.valueOf(newValue));
187     }
188     void observeUserName(LifecycleOwner lfo, Observer<String> o) {
189         authUserName.observe(lfo, o);
190     }
191     public String getAuthPassword() {
192         return authPassword.getValue();
193     }
194     void setAuthPassword(String newValue) {
195         if (!Misc.nullIsEmpty(newValue)
196                  .equals(Misc.nullIsEmpty(authPassword.getValue())))
197             authPassword.setValue(newValue);
198     }
199     void setAuthPassword(CharSequence newValue) {
200         setAuthPassword(String.valueOf(newValue));
201     }
202     void observePassword(LifecycleOwner lfo, Observer<String> o) {
203         authPassword.observe(lfo, o);
204     }
205     String getPreferredAccountsFilter() {
206         return preferredAccountsFilter.getValue();
207     }
208     void setPreferredAccountsFilter(String newValue) {
209         if (!Misc.nullIsEmpty(newValue)
210                  .equals(Misc.nullIsEmpty(preferredAccountsFilter.getValue())))
211             preferredAccountsFilter.setValue(newValue);
212     }
213     void setPreferredAccountsFilter(CharSequence newValue) {
214         setPreferredAccountsFilter(String.valueOf(newValue));
215     }
216     void observePreferredAccountsFilter(LifecycleOwner lfo, Observer<String> o) {
217         preferredAccountsFilter.observe(lfo, o);
218     }
219     int getThemeId() {
220         return themeId.getValue();
221     }
222     void setThemeId(int newValue) {
223         themeId.setValue(newValue);
224     }
225     void observeThemeId(LifecycleOwner lfo, Observer<Integer> o) {
226         themeId.observe(lfo, o);
227     }
228     void observeDetectingHledgerVersion(LifecycleOwner lfo, Observer<Boolean> o) {
229         detectingHledgerVersion.observe(lfo, o);
230     }
231     void setValuesFromProfile(Profile mProfile) {
232         if (mProfile != null) {
233             profileId.setValue(mProfile.getId());
234             profileName.setValue(mProfile.getName());
235             orderNo.setValue(mProfile.getOrderNo());
236             postingPermitted.setValue(mProfile.permitPosting());
237             showCommentsByDefault.setValue(mProfile.getShowCommentsByDefault());
238             showCommodityByDefault.setValue(mProfile.getShowCommodityByDefault());
239             {
240                 String comm = mProfile.getDefaultCommodity();
241                 if (TextUtils.isEmpty(comm))
242                     setDefaultCommodity(null);
243                 else
244                     setDefaultCommodity(comm);
245             }
246             futureDates.setValue(FutureDates.valueOf(mProfile.getFutureDates()));
247             apiVersion.setValue(API.valueOf(mProfile.getApiVersion()));
248             url.setValue(mProfile.getUrl());
249             useAuthentication.setValue(mProfile.useAuthentication());
250             authUserName.setValue(mProfile.useAuthentication() ? mProfile.getAuthUser() : "");
251             authPassword.setValue(mProfile.useAuthentication() ? mProfile.getAuthPassword() : "");
252             preferredAccountsFilter.setValue(mProfile.getPreferredAccountsFilter());
253             themeId.setValue(mProfile.getTheme());
254             detectedVersion.setValue(mProfile.detectedVersionPre_1_19() ? new HledgerVersion(true)
255                                                                         : new HledgerVersion(
256                                                                                 mProfile.getDetectedVersionMajor(),
257                                                                                 mProfile.getDetectedVersionMinor()));
258         }
259         else {
260             profileId.setValue(NO_PROFILE_ID);
261             orderNo.setValue(-1);
262             profileName.setValue(null);
263             url.setValue(HTTPS_URL_START);
264             postingPermitted.setValue(true);
265             showCommentsByDefault.setValue(true);
266             showCommodityByDefault.setValue(false);
267             setFutureDates(FutureDates.None);
268             setApiVersion(API.auto);
269             useAuthentication.setValue(false);
270             authUserName.setValue("");
271             authPassword.setValue("");
272             preferredAccountsFilter.setValue(null);
273             detectedVersion.setValue(null);
274         }
275     }
276     void updateProfile(Profile mProfile) {
277         mProfile.setId(profileId.getValue());
278         mProfile.setName(profileName.getValue());
279         mProfile.setOrderNo(orderNo.getValue());
280         mProfile.setUrl(url.getValue());
281         mProfile.setPermitPosting(postingPermitted.getValue());
282         mProfile.setShowCommentsByDefault(showCommentsByDefault.getValue());
283         mProfile.setDefaultCommodity(defaultCommodity.getValue());
284         mProfile.setShowCommodityByDefault(showCommodityByDefault.getValue());
285         mProfile.setPreferredAccountsFilter(preferredAccountsFilter.getValue());
286         mProfile.setUseAuthentication(useAuthentication.getValue());
287         mProfile.setAuthUser(authUserName.getValue());
288         mProfile.setAuthPassword(authPassword.getValue());
289         mProfile.setTheme(themeId.getValue());
290         mProfile.setFutureDates(futureDates.getValue()
291                                            .toInt());
292         mProfile.setApiVersion(apiVersion.getValue()
293                                          .toInt());
294         HledgerVersion version = detectedVersion.getValue();
295         mProfile.setDetectedVersionPre_1_19(version != null && version.isPre_1_20_1());
296         mProfile.setDetectedVersionMajor(version != null ? version.getMajor() : -1);
297         mProfile.setDetectedVersionMinor(version != null ? version.getMinor() : -1);
298     }
299     synchronized public void triggerVersionDetection() {
300         if (versionDetectionThread != null)
301             versionDetectionThread.interrupt();
302
303         versionDetectionThread = new VersionDetectionThread(this);
304         versionDetectionThread.start();
305     }
306     public LiveData<Long> getProfileId() {
307         return profileId;
308     }
309     static class VersionDetectionThread extends Thread {
310         static final int TARGET_PROCESS_DURATION = 1000;
311         private final Pattern versionPattern =
312                 Pattern.compile("^\"(\\d+)\\.(\\d+)(?:\\.(\\d+))?\"$");
313         private final ProfileDetailModel model;
314         public VersionDetectionThread(ProfileDetailModel model) {
315             this.model = model;
316         }
317         private HledgerVersion detectVersion() {
318             App.setAuthenticationDataFromProfileModel(model);
319             HttpURLConnection http;
320             try {
321                 http = NetworkUtil.prepareConnection(model.getUrl(), "version",
322                         model.getUseAuthentication());
323                 switch (http.getResponseCode()) {
324                     case 200:
325                         break;
326                     case 404:
327                         return new HledgerVersion(true);
328                     default:
329                         Logger.debug("profile", String.format(Locale.US,
330                                 "HTTP error detecting hledger-web version: [%d] %s",
331                                 http.getResponseCode(), http.getResponseMessage()));
332                         return null;
333                 }
334                 InputStream stream = http.getInputStream();
335                 BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
336                 String version = reader.readLine();
337                 Matcher m = versionPattern.matcher(version);
338                 if (m.matches()) {
339                     int major = Integer.parseInt(Objects.requireNonNull(m.group(1)));
340                     int minor = Integer.parseInt(Objects.requireNonNull(m.group(2)));
341                     final String patchText = m.group(3);
342                     final boolean hasPatch = patchText != null;
343                     int patch = hasPatch ? Integer.parseInt(patchText) : 0;
344
345                     return hasPatch ? new HledgerVersion(major, minor, patch)
346                                     : new HledgerVersion(major, minor);
347                 }
348                 else {
349                     Logger.debug("profile",
350                             String.format("Unrecognised version string '%s'", version));
351                     return null;
352                 }
353             }
354             catch (IOException e) {
355                 e.printStackTrace();
356                 return null;
357             }
358             finally {
359                 App.resetAuthenticationData();
360             }
361         }
362         @Override
363         public void run() {
364             model.detectingHledgerVersion.postValue(true);
365             try {
366                 long startTime = System.currentTimeMillis();
367
368                 final HledgerVersion version = detectVersion();
369
370                 long elapsed = System.currentTimeMillis() - startTime;
371                 Logger.debug("profile", "Detection duration " + elapsed);
372                 if (elapsed < TARGET_PROCESS_DURATION) {
373                     try {
374                         Thread.sleep(TARGET_PROCESS_DURATION - elapsed);
375                     }
376                     catch (InterruptedException e) {
377                         e.printStackTrace();
378                     }
379                 }
380                 model.detectedVersion.postValue(version);
381             }
382             finally {
383                 model.detectingHledgerVersion.postValue(false);
384             }
385         }
386     }
387 }