1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 | 1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
30x
30x
30x
30x
30x
30x
3x
3x
3x
7x
7x
7x
7x
7x
7x
7x
4x
1x
8x
8x
18x
8x
8x
6x
3x
3x
3x
3x
2x
1x
14x
14x
12x
39x
12x
3x
27x
27x
27x
20x
20x
20x
20x
20x
7x
6x
22x
6x
14x
1x
6x
22x
22x
22x
22x
6x
14x
1x
22x
22x
22x
22x
22x
22x
22x
1x
7x
7x
7x
7x
1x
| import '../../rxjs-extensions';
import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { forkJoin } from 'rxjs/observable/forkJoin';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { NgRedux, select } from '@angular-redux/store';
import { IAppState } from '../../store/state/AppState';
import { IFileInfo } from '../../model/fileInfo.model';
import { FormatService } from './format.service';
import { NameParsingService } from './name-parsing.service';
import { ListFormatterService } from '../list-formatter.service/list-formatter.service';
import { FileService } from '../file-service/file.service';
import { MigrationWorkBenchCommonModule, ToastrService, Logger, maskedTrim } from '../../common/mw.common.module';
import { IMeasureUpdate } from '../../model/measure.model';
import { PageActions } from '../../linqpad-review-pages/common/page.actions';
@Injectable()
export abstract class DataService {
@select(['config', 'baseDataUrl']) baseDataUrl$: Observable<string>;
@select(['file', 'fileList', 'files']) fileNames$: Observable<string[]>;
public abstract config$: Observable<any>;
public abstract files$: Observable<IFileInfo[]>;
protected abstract PAGE;
protected baseUrl;
protected filePrefixes;
constructor (
protected formatService: FormatService,
protected nameParsingService: NameParsingService,
protected listFormatterService: ListFormatterService,
protected fileService: FileService,
protected logger: Logger,
protected pageActions: PageActions,
) {}
public abstract getMeasure(): Observable<IMeasureUpdate>;
protected getBaseDataUrl$() {
return this.baseDataUrl$
.waitFor$()
.do(baseUrl => {
this.baseUrl = baseUrl;
});
}
public initializeList(numToInitialize, numToDisplay) {
this.pageActions.initializeListRequest();
this.fileService.getFileList(this.baseUrl);
const parsed$ = this.parsed$(this.filesOfType$);
const ordered$ = this.listFormatterService.process(parsed$);
const withContent$ = this.withContent$(ordered$, numToInitialize);
withContent$.subscribe(
(files) => { this.pageActions.initializeListSuccess(files, numToDisplay); },
(error) => { this.pageActions.initializeListFailed(error); }
);
}
get filesOfType$(): Observable<string[]> {
return this.fileNames$
.waitFor$() // ensures can move between Observable<fileInfo> and Observable<fileIno[]> with .toArray()
.map(files => files.filter(file => this.filePrefixes.some(value => file.startsWith(value))));
}
protected parsed$(files$: Observable<string[]>): Observable<IFileInfo[]> {
return files$.map(files =>
this.nameParsingService.parseFiles(files, this.filePrefixes)
);
}
public updateList(numToDisplay: number) {
this.pageActions.updateListRequest();
const files$ = this.files$.take(1); // read once - otherwise, endlessly loops due to self update
this.withContent$(files$, numToDisplay)
.subscribe(
(files) => { this.pageActions.updateListSuccess(files, numToDisplay); },
(error) => { this.pageActions.updateListFailed(error); }
);
}
private withContent$(files$: Observable<IFileInfo[]>, numToInitialize: number): Observable<IFileInfo[]> {
return files$.concatMap(files => {
const withContent$ = files.map((file, index) => {
return file.content || index >= numToInitialize
? Observable.of(file)
: this.getContent(file);
});
return forkJoin(...withContent$);
})
.catch(error => this.handleError(error, 'withContent$'));
}
public getContent(fileInfo: IFileInfo): Observable<IFileInfo> {
const url = this.baseUrl + fileInfo.name + '.html';
return this.fileService.getFile(url)
.map((res: Response) => {
const newFileInfo = {...fileInfo};
const date = res.headers.get('date');
newFileInfo.lastModified = date ? new Date(date) : null;
const content = res.text() || '';
return this.formatService.processContent(content, newFileInfo);
})
.catch(error => this.handleError(error, 'getContent'));
}
protected filesByDate(files) {
const keyFn = (file) => this.formatDate(file.effectiveDate);
return this.groupBy(files, keyFn)
.map(group => ({date: group.key, files: group.group }));
}
private groupBy(arr, keyFn) {
const grouped = arr.reduce( (grouping, item) => {
const key = keyFn(item);
grouping[key] = grouping[key] || [];
grouping[key].push(item);
return grouping;
}, {} );
return Object.keys(grouped)
.map(key => ({key: key, group: grouped[key]}));
}
private formatDate(date) {
// tslint:disable:prefer-const
let d = new Date(date),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear();
month = month.length < 2 ? '0' + month : month;
day = day.length < 2 ? '0' + day : day;
return [year, month, day].join('-');
}
private handleError(error: any, method: string) {
const caller = `${this.constructor.name}.${method}`;
this.logger.error(caller + ': ' + error);
error.caller = caller;
return Observable.throw(error);
}
}
|