]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailModel.java
TransactionAccumulator needs not keep a reference to the main model
[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<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() {
72     }
73     String getProfileName() {
74         return profileName.getValue();
75     }
76     void setProfileName(String newValue) {
77         if (!Misc.nullIsEmpty(newValue)
78                  .equals(Misc.nullIsEmpty(profileName.getValue())))
79             profileName.setValue(newValue);
80     }
81     void setProfileName(CharSequence newValue) {
82         setProfileName(String.valueOf(newValue));
83     }
84     void observeProfileName(LifecycleOwner lfo, Observer<String> o) {
85         profileName.observe(lfo, o);
86     }
87     Boolean getPostingPermitted() {
88         return postingPermitted.getValue();
89     }
90     void setPostingPermitted(boolean newValue) {
91         if (newValue != postingPermitted.getValue())
92             postingPermitted.setValue(newValue);
93     }
94     void observePostingPermitted(LifecycleOwner lfo, Observer<Boolean> o) {
95         postingPermitted.observe(lfo, o);
96     }
97     public void setShowCommentsByDefault(boolean newValue) {
98         if (newValue != showCommentsByDefault.getValue())
99             showCommentsByDefault.setValue(newValue);
100     }
101     void observeShowCommentsByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
102         showCommentsByDefault.observe(lfo, o);
103     }
104     FutureDates getFutureDates() {
105         return futureDates.getValue();
106     }
107     void setFutureDates(FutureDates newValue) {
108         if (newValue != futureDates.getValue())
109             futureDates.setValue(newValue);
110     }
111     void observeFutureDates(LifecycleOwner lfo, Observer<FutureDates> o) {
112         futureDates.observe(lfo, o);
113     }
114     String getDefaultCommodity() {
115         return defaultCommodity.getValue();
116     }
117     void setDefaultCommodity(String newValue) {
118         if (!Misc.equalStrings(newValue, defaultCommodity.getValue()))
119             defaultCommodity.setValue(newValue);
120     }
121     void observeDefaultCommodity(LifecycleOwner lfo, Observer<String> o) {
122         defaultCommodity.observe(lfo, o);
123     }
124     Boolean getShowCommodityByDefault() {
125         return showCommodityByDefault.getValue();
126     }
127     void setShowCommodityByDefault(boolean newValue) {
128         if (newValue != showCommodityByDefault.getValue())
129             showCommodityByDefault.setValue(newValue);
130     }
131     void observeShowCommodityByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
132         showCommodityByDefault.observe(lfo, o);
133     }
134     public Boolean getUseAuthentication() {
135         return useAuthentication.getValue();
136     }
137     void setUseAuthentication(boolean newValue) {
138         if (newValue != useAuthentication.getValue())
139             useAuthentication.setValue(newValue);
140     }
141     void observeUseAuthentication(LifecycleOwner lfo, Observer<Boolean> o) {
142         useAuthentication.observe(lfo, o);
143     }
144     API getApiVersion() {
145         return apiVersion.getValue();
146     }
147     void setApiVersion(API newValue) {
148         if (newValue != apiVersion.getValue())
149             apiVersion.setValue(newValue);
150     }
151     void observeApiVersion(LifecycleOwner lfo, Observer<API> o) {
152         apiVersion.observe(lfo, o);
153     }
154     HledgerVersion getDetectedVersion() { return detectedVersion.getValue(); }
155     void setDetectedVersion(HledgerVersion newValue) {
156         if (!Objects.equals(detectedVersion.getValue(), newValue))
157             detectedVersion.setValue(newValue);
158     }
159     void observeDetectedVersion(LifecycleOwner lfo, Observer<HledgerVersion> o) {
160         detectedVersion.observe(lfo, o);
161     }
162     public String getUrl() {
163         return url.getValue();
164     }
165     void setUrl(String newValue) {
166         if (!Misc.nullIsEmpty(newValue)
167                  .equals(Misc.nullIsEmpty(url.getValue())))
168             url.setValue(newValue);
169     }
170     void setUrl(CharSequence newValue) {
171         setUrl(String.valueOf(newValue));
172     }
173     void observeUrl(LifecycleOwner lfo, Observer<String> o) {
174         url.observe(lfo, o);
175     }
176     public String getAuthUserName() {
177         return authUserName.getValue();
178     }
179     void setAuthUserName(String newValue) {
180         if (!Misc.nullIsEmpty(newValue)
181                  .equals(Misc.nullIsEmpty(authUserName.getValue())))
182             authUserName.setValue(newValue);
183     }
184     void setAuthUserName(CharSequence newValue) {
185         setAuthUserName(String.valueOf(newValue));
186     }
187     void observeUserName(LifecycleOwner lfo, Observer<String> o) {
188         authUserName.observe(lfo, o);
189     }
190     public String getAuthPassword() {
191         return authPassword.getValue();
192     }
193     void setAuthPassword(String newValue) {
194         if (!Misc.nullIsEmpty(newValue)
195                  .equals(Misc.nullIsEmpty(authPassword.getValue())))
196             authPassword.setValue(newValue);
197     }
198     void setAuthPassword(CharSequence newValue) {
199         setAuthPassword(String.valueOf(newValue));
200     }
201     void observePassword(LifecycleOwner lfo, Observer<String> o) {
202         authPassword.observe(lfo, o);
203     }
204     String getPreferredAccountsFilter() {
205         return preferredAccountsFilter.getValue();
206     }
207     void setPreferredAccountsFilter(String newValue) {
208         if (!Misc.nullIsEmpty(newValue)
209                  .equals(Misc.nullIsEmpty(preferredAccountsFilter.getValue())))
210             preferredAccountsFilter.setValue(newValue);
211     }
212     void setPreferredAccountsFilter(CharSequence newValue) {
213         setPreferredAccountsFilter(String.valueOf(newValue));
214     }
215     void observePreferredAccountsFilter(LifecycleOwner lfo, Observer<String> o) {
216         preferredAccountsFilter.observe(lfo, o);
217     }
218     int getThemeId() {
219         return themeId.getValue();
220     }
221     void setThemeId(int newValue) {
222         themeId.setValue(newValue);
223     }
224     void observeThemeId(LifecycleOwner lfo, Observer<Integer> o) {
225         themeId.observe(lfo, o);
226     }
227     void observeDetectingHledgerVersion(LifecycleOwner lfo, Observer<Boolean> o) {
228         detectingHledgerVersion.observe(lfo, o);
229     }
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());
237             {
238                 String comm = mProfile.getDefaultCommodity();
239                 if (TextUtils.isEmpty(comm))
240                     setDefaultCommodity(null);
241                 else
242                     setDefaultCommodity(comm);
243             }
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()));
256         }
257         else {
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);
271         }
272     }
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()
287                                            .toInt());
288         mProfile.setApiVersion(apiVersion.getValue()
289                                          .toInt());
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);
294     }
295     synchronized public void triggerVersionDetection() {
296         if (versionDetectionThread != null)
297             versionDetectionThread.interrupt();
298
299         versionDetectionThread = new VersionDetectionThread(this);
300         versionDetectionThread.start();
301     }
302     public LiveData<Long> getProfileId() {
303         return profileId;
304     }
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) {
311             this.model = model;
312         }
313         private HledgerVersion detectVersion() {
314             App.setAuthenticationDataFromProfileModel(model);
315             HttpURLConnection http;
316             try {
317                 http = NetworkUtil.prepareConnection(model.getUrl(), "version",
318                         model.getUseAuthentication());
319                 switch (http.getResponseCode()) {
320                     case 200:
321                         break;
322                     case 404:
323                         return new HledgerVersion(true);
324                     default:
325                         Logger.debug("profile", String.format(Locale.US,
326                                 "HTTP error detecting hledger-web version: [%d] %s",
327                                 http.getResponseCode(), http.getResponseMessage()));
328                         return null;
329                 }
330                 InputStream stream = http.getInputStream();
331                 BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
332                 String version = reader.readLine();
333                 Matcher m = versionPattern.matcher(version);
334                 if (m.matches()) {
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;
339
340                     return hasPatch ? new HledgerVersion(major, minor, patch)
341                                     : new HledgerVersion(major, minor);
342                 }
343                 else {
344                     Logger.debug("profile",
345                             String.format("Unrecognised version string '%s'", version));
346                     return null;
347                 }
348             }
349             catch (IOException e) {
350                 e.printStackTrace();
351                 return null;
352             }
353             finally {
354                 App.resetAuthenticationData();
355             }
356         }
357         @Override
358         public void run() {
359             model.detectingHledgerVersion.postValue(true);
360             try {
361                 long startTime = System.currentTimeMillis();
362
363                 final HledgerVersion version = detectVersion();
364
365                 long elapsed = System.currentTimeMillis() - startTime;
366                 Logger.debug("profile", "Detection duration " + elapsed);
367                 if (elapsed < TARGET_PROCESS_DURATION) {
368                     try {
369                         Thread.sleep(TARGET_PROCESS_DURATION - elapsed);
370                     }
371                     catch (InterruptedException e) {
372                         e.printStackTrace();
373                     }
374                 }
375                 model.detectedVersion.postValue(version);
376             }
377             finally {
378                 model.detectingHledgerVersion.postValue(false);
379             }
380         }
381     }
382 }