Skip to content

Commit 026dd6b

Browse files
committed
wip legacy-data: Describe LegacyAppData type and deserializers; TODO test
1 parent a32e41d commit 026dd6b

File tree

2 files changed

+335
-0
lines changed

2 files changed

+335
-0
lines changed

lib/model/legacy_app_data.dart

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/// Logic for reading from the legacy app's data, on upgrade to this app.
2+
///
3+
/// Many of the details here correspond to specific parts of the
4+
/// legacy app's source code.
5+
/// See <https://github.com/zulip/zulip-mobile>.
6+
library;
7+
8+
import 'package:json_annotation/json_annotation.dart';
9+
10+
part 'legacy_app_data.g.dart';
11+
12+
/// Represents the data from the legacy app's database,
13+
/// so far as it's relevant for this app.
14+
///
15+
/// The full set of data in the legacy app's in-memory store is described by
16+
/// the type `GlobalState` in src/reduxTypes.js .
17+
/// Within that, the data it stores in the database is the data at the keys
18+
/// listed in `storeKeys` and `cacheKeys` in src/boot/store.js .
19+
/// The data under `cacheKeys` lives on the server and the app re-fetches it
20+
/// upon each startup anyway;
21+
/// so only the data under `storeKeys` is relevant for migrating to this app.
22+
///
23+
/// Within the data under `storeKeys`, some portions are also ignored
24+
/// for specific reasons described explicitly in comments on these types.
25+
@JsonSerializable()
26+
class LegacyAppData {
27+
// The `state.migrations` data gets read and used before attempting to
28+
// deserialize the data that goes into this class.
29+
// final LegacyAppMigrationsState migrations; // handled separately
30+
31+
final LegacyAppGlobalSettingsState? settings;
32+
final List<LegacyAppAccount>? accounts;
33+
34+
// final Map<??, String> drafts; // ignore; inherently transient
35+
36+
// final List<??> outbox; // ignore; inherently transient
37+
38+
LegacyAppData({
39+
required this.settings,
40+
required this.accounts,
41+
});
42+
43+
factory LegacyAppData.fromJson(Map<String, Object?> json) =>
44+
_$LegacyAppDataFromJson(json);
45+
46+
Map<String, Object?> toJson() => _$LegacyAppDataToJson(this);
47+
}
48+
49+
/// Corresponds to type `MigrationsState` in src/reduxTypes.js .
50+
@JsonSerializable()
51+
class LegacyAppMigrationsState {
52+
final int? version;
53+
54+
LegacyAppMigrationsState({required this.version});
55+
56+
factory LegacyAppMigrationsState.fromJson(Map<String, Object?> json) =>
57+
_$LegacyAppMigrationsStateFromJson(json);
58+
59+
Map<String, Object?> toJson() => _$LegacyAppMigrationsStateToJson(this);
60+
}
61+
62+
/// Corresponds to type `GlobalSettingsState` in src/reduxTypes.js .
63+
///
64+
/// The remaining data found at key `settings` in the overall data,
65+
/// described by type `PerAccountSettingsState`, lives on the server
66+
/// in the same way as the data under the keys in `cacheKeys`,
67+
/// and so is ignored here.
68+
@JsonSerializable()
69+
class LegacyAppGlobalSettingsState {
70+
final String language;
71+
final LegacyAppThemeSetting theme;
72+
final LegacyAppBrowserPreference browser;
73+
74+
// Ignored because the legacy app hadn't used it since 2017.
75+
// See discussion in commit zulip-mobile@761e3edb4 (from 2018).
76+
// final bool experimentalFeaturesEnabled; // ignore
77+
78+
final LegacyAppMarkMessagesReadOnScroll markMessagesReadOnScroll;
79+
80+
LegacyAppGlobalSettingsState({
81+
required this.language,
82+
required this.theme,
83+
required this.browser,
84+
required this.markMessagesReadOnScroll,
85+
});
86+
87+
factory LegacyAppGlobalSettingsState.fromJson(Map<String, Object?> json) =>
88+
_$LegacyAppGlobalSettingsStateFromJson(json);
89+
90+
Map<String, Object?> toJson() => _$LegacyAppGlobalSettingsStateToJson(this);
91+
}
92+
93+
/// Corresponds to type `ThemeSetting` in src/reduxTypes.js .
94+
enum LegacyAppThemeSetting {
95+
@JsonValue('default')
96+
default_,
97+
night;
98+
}
99+
100+
/// Corresponds to type `BrowserPreference` in src/reduxTypes.js .
101+
enum LegacyAppBrowserPreference {
102+
embedded,
103+
external,
104+
@JsonValue('default')
105+
default_,
106+
}
107+
108+
/// Corresponds to the type `GlobalSettingsState['markMessagesReadOnScroll']`
109+
/// in src/reduxTypes.js .
110+
@JsonEnum(fieldRename: FieldRename.kebab)
111+
enum LegacyAppMarkMessagesReadOnScroll {
112+
always, never, conversationViewsOnly,
113+
}
114+
115+
/// Corresponds to type `Account` in src/types.js .
116+
@JsonSerializable()
117+
class LegacyAppAccount {
118+
// These three come from type Auth in src/api/transportTypes.js .
119+
@_LegacyAppUrlJsonConverter()
120+
final Uri realm;
121+
final String apiKey;
122+
final String email;
123+
124+
final int? userId;
125+
126+
@_LegacyAppZulipVersionJsonConverter()
127+
final String? zulipVersion;
128+
129+
final int? zulipFeatureLevel;
130+
131+
final String? ackedPushToken;
132+
133+
// These three are ignored because this app doesn't currently have such
134+
// notices or banners for them to control; and because if we later introduce
135+
// such things, it's a pretty mild glitch to have them reappear, once,
136+
// after a once-in-N-years major upgrade to the app.
137+
// final DateTime? lastDismissedServerPushSetupNotice; // ignore
138+
// final DateTime? lastDismissedServerNotifsExpiringBanner; // ignore
139+
// final bool silenceServerPushSetupWarnings; // ignore
140+
141+
LegacyAppAccount({
142+
required this.realm,
143+
required this.apiKey,
144+
required this.email,
145+
required this.userId,
146+
required this.zulipVersion,
147+
required this.zulipFeatureLevel,
148+
required this.ackedPushToken,
149+
});
150+
151+
factory LegacyAppAccount.fromJson(Map<String, Object?> json) =>
152+
_$LegacyAppAccountFromJson(json);
153+
154+
Map<String, Object?> toJson() => _$LegacyAppAccountToJson(this);
155+
}
156+
157+
/// This and its subclasses correspond to portions of src/storage/replaceRevive.js .
158+
///
159+
/// (The rest of the conversions in that file are for types that don't appear
160+
/// in the portions of the legacy app's state we care about.)
161+
sealed class _LegacyAppJsonConverter<T> extends JsonConverter<T, Map<String, dynamic>> {
162+
const _LegacyAppJsonConverter();
163+
164+
String get serializedTypeName;
165+
166+
T fromJsonData(Object? json);
167+
168+
Object? toJsonData(T value);
169+
170+
/// Corresponds to `SERIALIZED_TYPE_FIELD_NAME`.
171+
static const _serializedTypeFieldName = '__serializedType__';
172+
173+
@override
174+
T fromJson(Map<String, dynamic> json) {
175+
final actualTypeName = json[_serializedTypeFieldName];
176+
if (actualTypeName != serializedTypeName) {
177+
throw FormatException("unexpected $_serializedTypeFieldName: $actualTypeName");
178+
}
179+
return fromJsonData(json['data']);
180+
}
181+
182+
@override
183+
Map<String, dynamic> toJson(T object) {
184+
return {
185+
_serializedTypeFieldName: serializedTypeName,
186+
'data': toJsonData(object),
187+
};
188+
}
189+
}
190+
191+
class _LegacyAppUrlJsonConverter extends _LegacyAppJsonConverter<Uri> {
192+
const _LegacyAppUrlJsonConverter();
193+
194+
@override
195+
String get serializedTypeName => 'URL';
196+
197+
@override
198+
Uri fromJsonData(Object? json) => Uri.parse(json as String);
199+
200+
@override
201+
Object? toJsonData(Uri value) => value.toString();
202+
}
203+
204+
/// Corresponds to type `ZulipVersion`.
205+
///
206+
/// This new app skips the parsing logic of the legacy app's ZulipVersion type,
207+
/// and just uses the raw string.
208+
class _LegacyAppZulipVersionJsonConverter extends _LegacyAppJsonConverter<String> {
209+
const _LegacyAppZulipVersionJsonConverter();
210+
211+
@override
212+
String get serializedTypeName => 'ZulipVersion';
213+
214+
@override
215+
String fromJsonData(Object? json) => json as String;
216+
217+
@override
218+
Object? toJsonData(String value) => value;
219+
}

lib/model/legacy_app_data.g.dart

Lines changed: 116 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)