11import StatDatabase from "@db/stat-database"
2+ import { AUTHOR_EMAIL } from "@src/package"
23import { isNotZeroResult } from "@util/stat"
34
45const statDatabase : StatDatabase = new StatDatabase ( chrome . storage . local )
56
6- export type OtherExtension = "web_activity_time_tracker"
7+ export type OtherExtension = "webtime_tracker" | " web_activity_time_tracker"
78
89export type ActionType = 'overwrite' | 'accumulate'
910
@@ -18,6 +19,8 @@ export type ImportedData = {
1819 rows : ImportedRow [ ]
1920}
2021
22+ const throwError = ( ) => { throw new Error ( "Failed to parse, please check your file or contact the author via " + AUTHOR_EMAIL ) }
23+
2124/**
2225 * Parse the content to rows
2326 *
@@ -26,20 +29,18 @@ export type ImportedData = {
2629 * @returns row data
2730 */
2831export async function parseFile ( ext : OtherExtension , file : File ) : Promise < ImportedData > {
29- const text = await file . text ( )
32+ let rows : ImportedRow [ ] = [ ]
33+ let focus = false
34+ let time = false
3035 if ( ext === 'web_activity_time_tracker' ) {
31- const lines = text . split ( '\n' ) . map ( line => line . trim ( ) ) . filter ( line => ! ! line ) . splice ( 1 )
32- const rows : ImportedRow [ ] = lines . map ( line => {
33- const [ host , date , seconds ] = line . split ( ',' ) . map ( cell => cell . trim ( ) )
34- const [ year , month , day ] = date . split ( '/' )
35- const realDate = `${ year } ${ month . length == 2 ? month : '0' + month } ${ day . length == 2 ? day : '0' + day } `
36- return { host, date : realDate , focus : parseInt ( seconds ) * 1000 }
37- } )
38- await doIfExist ( rows , ( row , exist ) => row . exist = exist )
39- return { rows, focus : true }
40- } else {
41- return { rows : [ ] }
36+ rows = await parseWebActivityTimeTracker ( file )
37+ focus = true
38+ } else if ( ext === 'webtime_tracker' ) {
39+ rows = await parseWebtimeTracker ( file )
40+ focus = true
4241 }
42+ await doIfExist ( rows , ( row , exist ) => row . exist = exist )
43+ return { rows, focus, time }
4344}
4445
4546async function doIfExist < T extends timer . stat . RowKey > ( items : T [ ] , processor : ( item : T , existVal : timer . stat . Result ) => any ) : Promise < void > {
@@ -50,6 +51,79 @@ async function doIfExist<T extends timer.stat.RowKey>(items: T[], processor: (it
5051 } ) )
5152}
5253
54+ async function parseWebActivityTimeTracker ( file : File ) : Promise < ImportedRow [ ] > {
55+ const text = await file . text ( )
56+ const lines = text . split ( '\n' ) . map ( line => line . trim ( ) ) . filter ( line => ! ! line ) . splice ( 1 )
57+ const rows : ImportedRow [ ] = lines . map ( line => {
58+ const [ host , date , seconds ] = line . split ( ',' ) . map ( cell => cell . trim ( ) )
59+ ! host || ! date || ( ! seconds && seconds !== '0' ) && throwError ( )
60+ const [ year , month , day ] = date . split ( '/' )
61+ ! year || ! month || ! day && throwError ( )
62+ const realDate = `${ year } ${ month . length == 2 ? month : '0' + month } ${ day . length == 2 ? day : '0' + day } `
63+ return { host, date : realDate , focus : parseInt ( seconds ) * 1000 }
64+ } )
65+ return rows
66+ }
67+
68+ type WebtimeTrackerBackup = {
69+ content : {
70+ domains : {
71+ [ domain : string ] : {
72+ name : string
73+ days : {
74+ // date format: 2023-07-22
75+ [ date : string ] : { seconds : number }
76+ }
77+ }
78+ }
79+ }
80+ }
81+
82+ const WEBTIME_TRACKER_DATE_REG = / ( \d { 2 } ) - ( \d { 2 } ) - \d { 2 } /
83+ const cvtWebtimeTrackerDate = ( date : string ) : string => WEBTIME_TRACKER_DATE_REG . test ( date ) ? date . split ( '-' ) . join ( '' ) : undefined
84+
85+ async function parseWebtimeTracker ( file : File ) : Promise < ImportedRow [ ] > {
86+ const text = await file . text ( )
87+ if ( isJsonFile ( file ) ) {
88+ // JSON file by backup
89+ const data = JSON . parse ( text ) as WebtimeTrackerBackup
90+ const domains = data ?. content ?. domains || { }
91+ const rows : ImportedRow [ ] = Object . entries ( domains )
92+ . flatMap (
93+ ( [ host , value ] ) => Object . entries ( value ?. days || { } )
94+ . map ( ( [ date , item ] ) => [ host , cvtWebtimeTrackerDate ( date ) , item ?. seconds ] as [ string , string , number ] )
95+ )
96+ . filter ( ( [ host , date , seconds ] ) => host && date && seconds )
97+ . map ( ( [ host , date , seconds ] ) => ( {
98+ host,
99+ date,
100+ focus : seconds * 1000
101+ } as ImportedRow ) )
102+ console . log ( rows )
103+ return rows
104+ } else if ( isCsvFile ( file ) ) {
105+ const lines = text . split ( '\n' ) . map ( line => line . trim ( ) ) . filter ( line => ! ! line )
106+ const colHeaders = lines [ 0 ] . split ( ',' )
107+ const rows : ImportedRow [ ] = [ ]
108+ lines . slice ( 1 ) . forEach ( line => {
109+ const cells = line . split ( ',' )
110+ const host = cells [ 0 ]
111+ if ( ! host ) return
112+ for ( let i = 1 ; i < colHeaders ?. length ; i ++ ) {
113+ const seconds = Number . parseInt ( cells [ i ] )
114+ const date = cvtWebtimeTrackerDate ( colHeaders [ i ] )
115+ seconds && date && rows . push ( { host, date, focus : seconds * 1000 } )
116+ }
117+ } )
118+ return rows
119+ }
120+ throw new Error ( "Invalid file format" )
121+ }
122+
123+ const isJsonFile = ( file : File ) : boolean => file ?. type ?. startsWith ( 'application/json' )
124+
125+ const isCsvFile = ( file : File ) : boolean => file ?. type ?. startsWith ( 'text/csv' )
126+
53127/**
54128 * Import data
55129 */
0 commit comments