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