@aerogel/plugin-local-first 0.1.1 → 0.1.2-next.2c3536aed027b7db949ee77b64a6cf2dc1281eaf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/aerogel-plugin-local-first.d.ts +49 -180
- package/dist/aerogel-plugin-local-first.js +528 -1309
- package/dist/aerogel-plugin-local-first.js.map +1 -1
- package/package.json +52 -49
- package/src/components/AccountLoggedIn.vue +1 -3
- package/src/components/AccountModal.vue +6 -10
- package/src/components/settings/PurgeData.vue +16 -11
- package/src/components/settings/PurgeMetadata.vue +41 -0
- package/src/components/settings/index.ts +8 -0
- package/src/index.ts +12 -6
- package/src/lib/SyncQueue.ts +21 -40
- package/src/lib/models.ts +58 -0
- package/src/services/Cloud.state.ts +8 -68
- package/src/services/Cloud.ts +204 -224
- package/src/jobs/Backup.ts +0 -98
- package/src/jobs/Migrate.test.ts +0 -217
- package/src/jobs/Migrate.ts +0 -186
- package/src/jobs/Sync.test.ts +0 -1260
- package/src/jobs/Sync.ts +0 -696
- package/src/jobs/mixins/LoadsChildren.ts +0 -227
- package/src/jobs/mixins/LoadsTypeIndex.ts +0 -25
- package/src/jobs/mixins/TracksLocalModels.ts +0 -32
- package/src/lib/inference.ts +0 -124
- package/src/lib/mirroring.ts +0 -351
- package/src/services/DocumentsCache.ts +0 -39
- package/src/testing/cloud.ts +0 -193
- package/src/testing/mocks/Solid.mock.ts +0 -34
- package/src/testing/setup.ts +0 -35
- package/src/testing/stubs/LegacyTask.schema.ts +0 -15
- package/src/testing/stubs/Movie.schema.ts +0 -15
- package/src/testing/stubs/Movie.ts +0 -3
- package/src/testing/stubs/MoviesContainer.ts +0 -20
- package/src/testing/stubs/Person.schema.ts +0 -9
- package/src/testing/stubs/Person.ts +0 -18
- package/src/testing/stubs/Post.schema.ts +0 -17
- package/src/testing/stubs/Post.ts +0 -18
- package/src/testing/stubs/Task.schema.ts +0 -12
- package/src/testing/stubs/Task.ts +0 -3
- package/src/testing/stubs/TasksList.ts +0 -20
- package/src/testing/stubs/Workspace.ts +0 -15
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Noel De Martin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,31 +1,19 @@
|
|
|
1
|
-
import { AcceptRefs } from '@aerogel/core';
|
|
2
1
|
import { Authenticator } from '@aerogel/plugin-solid';
|
|
3
2
|
import { ComponentOptionsMixin } from 'vue';
|
|
4
3
|
import { ComponentProvideOptions } from 'vue';
|
|
5
|
-
import { Cr } from '@noeldemartin/utils';
|
|
6
4
|
import { DefineComponent } from 'vue';
|
|
7
|
-
import { Engine } from 'soukai';
|
|
5
|
+
import { Engine } from 'soukai-bis';
|
|
8
6
|
import { ErrorSource } from '@aerogel/core';
|
|
9
7
|
import { Facade } from '@noeldemartin/utils';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { ModalExpose } from '@aerogel/core';
|
|
8
|
+
import { JobListener } from 'soukai-bis';
|
|
9
|
+
import { Model } from 'soukai-bis';
|
|
10
|
+
import { ModelConstructor } from 'soukai-bis';
|
|
14
11
|
import { Nullable } from '@noeldemartin/utils';
|
|
15
|
-
import { ObjectsMap } from '@noeldemartin/utils';
|
|
16
12
|
import { Plugin as Plugin_2 } from '@aerogel/core';
|
|
17
13
|
import { PublicProps } from 'vue';
|
|
18
|
-
import { Replace } from '@aerogel/core';
|
|
19
14
|
import { Semaphore } from '@noeldemartin/utils';
|
|
20
15
|
import { ServiceWithState } from '@aerogel/core';
|
|
21
|
-
import {
|
|
22
|
-
import { SolidContainer } from 'soukai-solid';
|
|
23
|
-
import { SolidDocument } from 'soukai-solid';
|
|
24
|
-
import { SolidModel } from 'soukai-solid';
|
|
25
|
-
import { SolidModelConstructor } from 'soukai-solid';
|
|
26
|
-
import { SolidSchemaDefinition } from 'soukai-solid';
|
|
27
|
-
import { SolidTypeIndex } from 'soukai-solid';
|
|
28
|
-
import { Tombstone } from 'soukai-solid';
|
|
16
|
+
import { Sync } from 'soukai-bis';
|
|
29
17
|
|
|
30
18
|
export declare const Account: DefineComponent< {}, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
|
|
31
19
|
|
|
@@ -35,46 +23,48 @@ export declare const AccountLoggedOut: DefineComponent< {}, {}, {}, {}, {}, C
|
|
|
35
23
|
|
|
36
24
|
export declare const AccountLoginModal: DefineComponent< {}, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
|
|
37
25
|
|
|
38
|
-
export declare const AccountModal: DefineComponent< {}, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, true, {
|
|
39
|
-
$modalRef: ShallowUnwrapRef<AcceptRefs<ModalExpose<void>>> | null;
|
|
40
|
-
}, any>;
|
|
26
|
+
export declare const AccountModal: DefineComponent< {}, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
|
|
41
27
|
|
|
42
28
|
export declare const Cloud: Facade<CloudService>;
|
|
43
29
|
|
|
30
|
+
export declare type CloudRegistration = boolean | RegisterOptions | undefined;
|
|
31
|
+
|
|
44
32
|
export declare class CloudService extends _default {
|
|
45
33
|
protected asyncLock: Semaphore;
|
|
46
34
|
protected engine: Engine | null;
|
|
47
|
-
protected pollingInterval:
|
|
35
|
+
protected pollingInterval: ReturnType<typeof setInterval> | null;
|
|
48
36
|
whenReady<T>(callback: () => T): Promise<T>;
|
|
49
|
-
setup(
|
|
37
|
+
setup(): Promise<void>;
|
|
38
|
+
getEngine(): Engine | null;
|
|
39
|
+
requireEngine(): Engine;
|
|
50
40
|
dismissSetup(): void;
|
|
51
|
-
dismissMigration(): void;
|
|
52
|
-
postponeMigration(): void;
|
|
53
41
|
syncIfOnline(options?: SyncOptions): Promise<void>;
|
|
54
|
-
syncIfOnline(model:
|
|
55
|
-
syncIfOnline(models:
|
|
42
|
+
syncIfOnline(model: Model): Promise<void>;
|
|
43
|
+
syncIfOnline(models: Model[]): Promise<void>;
|
|
56
44
|
sync(options?: SyncOptions): Promise<void>;
|
|
57
|
-
sync(model:
|
|
58
|
-
sync(models:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
requireRemoteCollection(modelClass: SolidModelConstructor): string;
|
|
45
|
+
sync(model: Model): Promise<void>;
|
|
46
|
+
sync(models: Model[]): Promise<void>;
|
|
47
|
+
track(modelClass: ModelConstructor, options?: {
|
|
48
|
+
register?: CloudRegistration;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
requireRemoteContainerUrl(modelClass: ModelConstructor): string;
|
|
64
51
|
protected boot(): Promise<void>;
|
|
65
|
-
protected getDirtyLocalModels():
|
|
66
|
-
protected getModelsIn(urls: string[], model:
|
|
67
|
-
protected requireEngine(): Engine;
|
|
52
|
+
protected getDirtyLocalModels(): Model[];
|
|
53
|
+
protected getModelsIn(urls: string[], model: Model): Model[];
|
|
68
54
|
protected setReady(ready: boolean): Promise<void>;
|
|
69
|
-
protected
|
|
55
|
+
protected migrateLocalUrls(): Promise<void>;
|
|
70
56
|
protected onApplicationReady(): Promise<void>;
|
|
71
57
|
protected onApplicationMounted(): Promise<void>;
|
|
72
58
|
protected login(authenticator: Authenticator): void;
|
|
73
|
-
protected logout(): void
|
|
59
|
+
protected logout(): Promise<void>;
|
|
74
60
|
private watchNetworkStatus;
|
|
75
61
|
private syncOnStartup;
|
|
76
|
-
private
|
|
77
|
-
private
|
|
62
|
+
private setupRemoteCollection;
|
|
63
|
+
private trackModels;
|
|
64
|
+
private refreshTrackedModels;
|
|
65
|
+
private clearLocalModelUpdates;
|
|
66
|
+
private onModelCreated;
|
|
67
|
+
private onModelUpdated;
|
|
78
68
|
}
|
|
79
69
|
|
|
80
70
|
declare const CloudStatus: {
|
|
@@ -82,21 +72,17 @@ declare const CloudStatus: {
|
|
|
82
72
|
readonly Offline: "offline";
|
|
83
73
|
readonly Online: "online";
|
|
84
74
|
readonly Syncing: "syncing";
|
|
85
|
-
readonly Migrating: "migrating";
|
|
86
75
|
};
|
|
87
76
|
|
|
88
77
|
declare const _default: ServiceWithState< {
|
|
89
78
|
autoPush: boolean;
|
|
90
79
|
localModelUpdates: Record<string, number>;
|
|
91
|
-
|
|
92
|
-
migrationJob: Nullable<Migrate>;
|
|
93
|
-
migrationPostponed: boolean;
|
|
94
|
-
modelCollections: Record<string, string[]>;
|
|
80
|
+
rootContainerUrls: Record<string, string>;
|
|
95
81
|
pollingEnabled: boolean;
|
|
96
82
|
pollingMinutes: number;
|
|
97
83
|
ready: boolean;
|
|
84
|
+
manualSetup: boolean;
|
|
98
85
|
registeredModels: ModelRegistration[];
|
|
99
|
-
schemaMigrations: Map<SolidModelConstructor, SolidModelConstructor | SolidSchemaDefinition>;
|
|
100
86
|
setupDismissed: boolean;
|
|
101
87
|
setupOngoing: boolean;
|
|
102
88
|
startupSync: boolean;
|
|
@@ -108,171 +94,57 @@ disconnected: boolean;
|
|
|
108
94
|
online: boolean;
|
|
109
95
|
offline: boolean;
|
|
110
96
|
syncing: boolean;
|
|
111
|
-
migrating: boolean;
|
|
112
97
|
localChanges: number;
|
|
113
98
|
dirty: boolean;
|
|
114
|
-
|
|
115
|
-
migrationDisabledReason: string | null;
|
|
116
|
-
}, Replace<Partial<{
|
|
99
|
+
}, Partial<{
|
|
117
100
|
autoPush: boolean;
|
|
118
101
|
localModelUpdates: Record<string, number>;
|
|
119
|
-
|
|
120
|
-
migrationJob: Nullable<Migrate>;
|
|
121
|
-
migrationPostponed: boolean;
|
|
122
|
-
modelCollections: Record<string, string[]>;
|
|
102
|
+
rootContainerUrls: Record<string, string>;
|
|
123
103
|
pollingEnabled: boolean;
|
|
124
104
|
pollingMinutes: number;
|
|
125
105
|
ready: boolean;
|
|
106
|
+
manualSetup: boolean;
|
|
126
107
|
registeredModels: ModelRegistration[];
|
|
127
|
-
schemaMigrations: Map<SolidModelConstructor, SolidModelConstructor | SolidSchemaDefinition>;
|
|
128
108
|
setupDismissed: boolean;
|
|
129
109
|
setupOngoing: boolean;
|
|
130
110
|
startupSync: boolean;
|
|
131
111
|
status: TCloudStatus;
|
|
132
112
|
syncError: Nullable<ErrorSource>;
|
|
133
113
|
syncJob: Nullable<Sync>;
|
|
134
|
-
}>, {
|
|
135
|
-
migrationJob: MigrateJobStatus | undefined;
|
|
136
114
|
}>>;
|
|
137
115
|
|
|
138
|
-
declare
|
|
139
|
-
protected localModels?: ObjectsMap<SolidModel>;
|
|
140
|
-
protected loadContainedModels(model: SolidContainer, options?: {
|
|
141
|
-
ignoreTombstones?: boolean;
|
|
142
|
-
status?: ResourceJobStatus;
|
|
143
|
-
onDocumentLoaded?: () => unknown;
|
|
144
|
-
onDocumentError?: (error: unknown) => unknown;
|
|
145
|
-
onDocumentRead?: (document: SolidDocument) => unknown;
|
|
146
|
-
}): Promise<SolidModel[]>;
|
|
147
|
-
protected loadDocumentChildren(model: SolidContainer, documentUrl: string, documents: ObjectsMap<SolidDocument>, onDocumentError?: (error: unknown) => unknown, onDocumentRead?: (document: SolidDocument) => unknown): Promise<{
|
|
148
|
-
children: Record<string, SolidModel[]>;
|
|
149
|
-
tombstones: Tombstone[];
|
|
150
|
-
}>;
|
|
151
|
-
protected loadDocumentChildrenFromLocal(model: SolidModel, document: SolidDocument): Promise<Record<string, SolidModel[]>>;
|
|
152
|
-
protected loadDocumentChildrenFromRemote(model: SolidContainer, documentUrl: string, onDocumentError?: (error: unknown) => unknown, onDocumentRead?: (document: SolidDocument) => unknown): Promise<{
|
|
153
|
-
children: Record<string, SolidModel[]>;
|
|
154
|
-
tombstones: Tombstone[];
|
|
155
|
-
}>;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
declare class LoadsTypeIndex {
|
|
159
|
-
protected loadTypeIndex(options?: {
|
|
160
|
-
create?: false;
|
|
161
|
-
fresh?: boolean;
|
|
162
|
-
}): Promise<SolidTypeIndex | null>;
|
|
163
|
-
protected loadTypeIndex(options?: {
|
|
164
|
-
create?: true;
|
|
165
|
-
fresh?: boolean;
|
|
166
|
-
}): Promise<SolidTypeIndex>;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
declare function localFirst(): Plugin_2;
|
|
116
|
+
declare function localFirst(options?: Options): Plugin_2;
|
|
170
117
|
export default localFirst;
|
|
171
118
|
|
|
172
119
|
export declare type LocalFirstServices = typeof services;
|
|
173
120
|
|
|
174
|
-
declare class Migrate extends Migrate_base {
|
|
175
|
-
static restore(status: MigrateJobStatus): Migrate;
|
|
176
|
-
constructor();
|
|
177
|
-
protected _models?: SolidModel[];
|
|
178
|
-
get models(): SolidModel[] | undefined;
|
|
179
|
-
set models(models: SolidModel[] | undefined);
|
|
180
|
-
protected run(): Promise<void>;
|
|
181
|
-
protected migrateRegisteredModel(model: SolidModel, status: MigrateModelJobStatus, typeIndex?: Nullable<SolidTypeIndex>): Promise<void>;
|
|
182
|
-
protected migrate(localModel: SolidModel, status: MigrateModelJobStatus): Promise<void>;
|
|
183
|
-
protected migrateChildren(localModel: SolidModel, status: MigrateModelJobStatus): Promise<void>;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
declare const Migrate_base: typeof Job<JobListener, MigrateJobStatus, MigrateJobStatus> & Cr<LoadsChildren & LoadsTypeIndex & TracksLocalModels>;
|
|
187
|
-
|
|
188
|
-
declare interface MigrateJobStatus extends JobStatus {
|
|
189
|
-
children?: MigrateModelJobStatus[];
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
declare interface MigrateModelJobStatus extends JobStatus {
|
|
193
|
-
modelUrl: string;
|
|
194
|
-
children?: MigrateModelJobStatus[];
|
|
195
|
-
}
|
|
196
|
-
|
|
197
121
|
declare interface ModelRegistration {
|
|
198
|
-
modelClass:
|
|
122
|
+
modelClass: ModelConstructor;
|
|
199
123
|
path?: string;
|
|
200
124
|
}
|
|
201
125
|
|
|
202
|
-
export declare interface
|
|
203
|
-
|
|
126
|
+
export declare interface Options {
|
|
127
|
+
manualSetup?: boolean;
|
|
204
128
|
}
|
|
205
129
|
|
|
206
|
-
declare interface
|
|
207
|
-
|
|
208
|
-
|
|
130
|
+
export declare interface RegisterOptions {
|
|
131
|
+
path?: string;
|
|
132
|
+
depth?: number;
|
|
209
133
|
}
|
|
210
134
|
|
|
211
135
|
declare const services: {
|
|
212
136
|
$cloud: Facade<CloudService>;
|
|
213
137
|
};
|
|
214
138
|
|
|
215
|
-
declare class Sync extends Sync_base {
|
|
216
|
-
readonly documentsWithErrors: Set<string>;
|
|
217
|
-
protected models?: SolidModel[];
|
|
218
|
-
protected dirtyRemoteModels?: SolidModel[];
|
|
219
|
-
protected rootLocalModels: SolidModel[];
|
|
220
|
-
protected tombstones: ObjectsMap<Tombstone>;
|
|
221
|
-
protected syncedModelUrls: Set<string>;
|
|
222
|
-
protected documentsModifiedAt: Record<string, Date>;
|
|
223
|
-
protected screenLock: Promise<void | {
|
|
224
|
-
release(): Promise<void>;
|
|
225
|
-
}> | null;
|
|
226
|
-
protected localModels: ObjectsMap<SolidModel>;
|
|
227
|
-
constructor(models?: SolidModel[]);
|
|
228
|
-
protected run(): Promise<void>;
|
|
229
|
-
protected lockScreen(): void;
|
|
230
|
-
protected releaseScreen(): void;
|
|
231
|
-
protected getInitialStatus(): SyncJobStatus;
|
|
232
|
-
protected calculateCurrentProgress(status?: JobStatus): number;
|
|
233
|
-
private pullChanges;
|
|
234
|
-
private pushChanges;
|
|
235
|
-
private fetchRemoteModels;
|
|
236
|
-
private fetchRemoteModelsForLocal;
|
|
237
|
-
private getTypeRegistrations;
|
|
238
|
-
private updateTypeRegistrations;
|
|
239
|
-
private loadChildren;
|
|
240
|
-
private deleteChild;
|
|
241
|
-
private saveModelAndChildren;
|
|
242
|
-
private synchronizeModels;
|
|
243
|
-
private reconcileInconsistencies;
|
|
244
|
-
private synchronizeContainerDocuments;
|
|
245
|
-
private rememberModelDocuments;
|
|
246
|
-
private ignoringDocumentErrors;
|
|
247
|
-
private handleDocumentError;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
declare const Sync_base: typeof Job<JobListener, SyncJobStatus, SyncJobStatus> & Cr<LoadsChildren & LoadsTypeIndex & TracksLocalModels>;
|
|
251
|
-
|
|
252
|
-
declare interface SyncJobStatus extends JobStatus {
|
|
253
|
-
root: true;
|
|
254
|
-
children: [SyncPullJobStatus, JobStatus];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
139
|
export declare interface SyncOptions extends JobListener {
|
|
258
|
-
model?:
|
|
259
|
-
models?:
|
|
140
|
+
model?: Model;
|
|
141
|
+
models?: Model[];
|
|
260
142
|
refreshUserProfile?: boolean;
|
|
261
143
|
syncDirty?: boolean;
|
|
262
144
|
}
|
|
263
145
|
|
|
264
|
-
declare interface SyncPullJobStatus extends JobStatus {
|
|
265
|
-
children?: ResourceJobStatus[];
|
|
266
|
-
}
|
|
267
|
-
|
|
268
146
|
declare type TCloudStatus = (typeof CloudStatus)[keyof typeof CloudStatus];
|
|
269
147
|
|
|
270
|
-
declare class TracksLocalModels {
|
|
271
|
-
protected localModels: ObjectsMap<SolidModel>;
|
|
272
|
-
protected indexLocalModels(models: SolidModel[]): Promise<void>;
|
|
273
|
-
protected addLocalModel(localModel: SolidModel): Promise<void>;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
148
|
export { }
|
|
277
149
|
|
|
278
150
|
|
|
@@ -287,17 +159,14 @@ declare module '@aerogel/core' {
|
|
|
287
159
|
'cloud:ready': void;
|
|
288
160
|
'cloud:backup-started': void;
|
|
289
161
|
'cloud:backup-completed': void;
|
|
290
|
-
'cloud:
|
|
291
|
-
'cloud:
|
|
292
|
-
'cloud:migration-completed': SolidModel[];
|
|
293
|
-
'cloud:sync-started': SolidModel[] | undefined;
|
|
294
|
-
'cloud:sync-completed': SolidModel[] | undefined;
|
|
162
|
+
'cloud:sync-started': Model[] | undefined;
|
|
163
|
+
'cloud:sync-completed': Model[] | undefined;
|
|
295
164
|
}
|
|
296
165
|
}
|
|
297
166
|
|
|
298
167
|
|
|
299
|
-
declare module 'soukai-
|
|
300
|
-
namespace
|
|
301
|
-
const cloud:
|
|
168
|
+
declare module 'soukai-bis' {
|
|
169
|
+
namespace Model {
|
|
170
|
+
const cloud: CloudRegistration;
|
|
302
171
|
}
|
|
303
172
|
}
|