Skip to content

Commit 9bcb2a5

Browse files
committed
legacy-data: Describe LegacyAppData type and deserializers
1 parent 76e64be commit 9bcb2a5

File tree

2 files changed

+336
-0
lines changed

2 files changed

+336
-0
lines changed

lib/model/legacy_app_data.dart

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

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)