· 6 years ago · Oct 08, 2019, 07:04 AM
1class MonolithicData{
2 //
3 // Intention:
4 //
5 // Accepts request to load Steam Profile
6 // Fill keys and indexs apon fetch
7 // ?? Callbacks for React state updates ??
8 // ?? error reporting (also callbacks) ??
9 // Generates futher requests for AppIds
10 // Merge addional data, fills indexes
11 // Some schedule to load AppIDs in batchs of 10...50 or so,
12 // With retries, ?? and errors ??
13 // Lazy loading not an option, need all tags and catagories from each app
14 // Note: App request can be batched by Api server.
15 //
16 // This looks like a DB because it is...
17 // It might also be an awkardly combined Model, Controller and JobQueue.
18 // Seems like a naive approuch - scope is too big ...possible existential horror imminent?
19 //
20 // Proceeding as an exercise:
21 // To help define schema for use with something better.
22 //
23 // Options to consider for removing this mess:
24 // RxJs + Database [LokiJS, TaffyDB, AlaSQL]
25 // WatermelonDB - an intergrated react solution.
26 //
27 constructor(){
28 // :: Tables ::
29 // SteamId : []{AppId, HoursForever, LastPlayed}
30 this.profiles = {}
31 // AppId: {Title, Logo Tags, Categories, Genres}
32 this.apps = {}
33
34 // :: Indexes ::
35 // SteamId : [AppId]
36 this.INX_profile_app ={}
37 // AppId : [SteamId]
38 this.INX_app_profile ={}
39 // etc...
40 this.INX_tag_app = {}
41 this.INX_app_tag = {}
42 this.INX_cat_app = {}
43 this.INX_app_cat = {}
44 this.INX_gen_app = {}
45 this.INX_app_gen = {}
46
47 // uhhhh, this is pretty ugly
48 // [AppId+"_"+steamId] = index of app in profiles[steamId]
49 this.INX_profileapp_inx ={}
50
51 // ?? how do we use this ??
52 this.errors = []
53
54 // :: Schedule ::
55 this.appQueue = []
56
57
58 // janky callback
59 this.callbacks = {}
60 }
61
62 indexAdder(dest, key, valArray, noArrayWrap){
63 // if dest has key: merge array; else : insert
64 if (Array.isArray(valArray)){
65 if (key in dest){
66 // merging arrays in JS, https://stackoverflow.com/questions/1584370/
67 dest[key] = [...new Set([...dest[key], ...valArray ])]
68 }else{
69 dest[key] = valArray
70 }
71 }else{
72 if (key in dest){
73 dest[key] = [...new Set([...dest[key], valArray ])]
74 }else{
75 dest[key] = [valArray]
76 }
77 }
78 }
79
80 profileChannel(steamId, data){
81 console.log("profileChannel() start", steamId)
82 try{
83 // store list of AppsId to fetch
84 var toQueueApps = []
85 let profile = []
86 for (let i =0 ; i < data.length; i++) {
87 let app = data[i]
88
89 const {AppId, Title, Logo} = app
90
91 this.indexAdder(this.INX_profile_app, steamId, AppId)
92 this.indexAdder(this.INX_profileapp_inx, AppId+"_"+steamId, i, true)
93 this.indexAdder(this.INX_app_profile, AppId, steamId)
94
95 this.appAddMerge(AppId, {Logo, Title})
96
97 toQueueApps.push(AppId)
98
99 // profile.push( {AppId, Title, HoursForever, LastPlayed} )
100 profile.push( app )
101 }
102
103 this.profiles[steamId] = profile;
104
105 this.fireProfileCallbacks(steamId)
106 // queue fetching of app entries
107 this.queueApps(toQueueApps)
108 console.log("profileChannel() complete.", steamId)
109 }
110 catch(error) {
111 console.log("profileChannel error.", error);
112 }
113 }
114
115 fireProfileCallbacks(steamId){
116 let cbkey = "profile_"+steamId
117 // console.log("fireProfileCallbacks", this.callbacks)
118 for (var i = 0; i<this.callbacks[cbkey].length;i++){
119 let cb = this.callbacks[cbkey][i]
120 // console.log(cb)
121 cb(steamId)
122 }
123 }
124
125 addCallbackProfileLoad(f, steamId){
126 this.indexAdder(
127 this.callbacks,
128 ["profile_"+steamId],
129 ()=>f(this.viewProfile(steamId))
130 )
131 }
132
133 viewProfile(steamId){
134 // This is pretty completly unnessisary
135 // return this.INX_profile_app[steamId].map((AppId)=>{
136 // let app_inx = this.INX_profileapp_inx[AppId+"_"+steamId]
137 // let x = this.profiles[steamId][app_inx]
138 // return x
139 // })
140 return this.profiles[steamId].map((entry)=>{
141 let ne = entry
142 let Tags = this.INX_app_tag[ne.AppId]
143 let Gens = this.INX_app_gen[ne.AppId]
144 let Cats =this.INX_app_cat[ne.AppId]
145 return Object.assign(ne, {Tags, Gens, Cats})
146 })
147 }
148
149
150 profileLoad(steamId){
151 const self = this // Throw `this` into closure because I forget the correct solution
152
153 //[ {AppId, Title, Logo, HoursForever, LastPlayed} ]
154 fetch("/api/profile?q="+steamId)
155 .then(res => {
156 if(res.ok){
157 return res.json()
158 }else{
159 self.addError(steamId, {code:res.code, msg:res.statusText})
160 }
161 })
162 .then(data => {
163 console.log("monodata.profileLoad() request success")
164 self.profileChannel(steamId, data)
165
166 })
167 .catch((e) => {
168 self.addError(steamId, {code:999, msg:"Server Down"} )
169 });
170 }
171
172
173 appAddMerge(AppId, entry){
174 //const [AppId, Title, Tags, Categories, Genres] = app
175 if (AppId in this.apps){
176 Object.assign(this.apps[AppId], entry)
177 }else{
178 this.apps[AppId] = entry
179 }
180 }
181
182 appChannel(appArray){
183 for (let app in appArray){
184 this.appAddMerge(app["AppId"], app)
185 }
186 }
187
188
189 appsLoad(appArray){
190 const self = this // Throw `this` into closure because I forget the correct solution
191
192 let apps = appArray.join(',');
193 // [{AppId, Title, Tags, Categories, Genres}]
194 fetch("/api/app?q="+apps)
195 .then(res => {
196 if(res.ok){
197 return res.json()
198 }else{
199 self.addError('app', appArray, {code:res.code, msg:res.statusText})
200 }
201 })
202 .then(data => {
203 self.appChannel(data)
204 })
205 .catch((e) => {
206 self.addError('app', appArray, {code:999, msg:"Server Down"} )
207 });
208 }
209
210 addError(type, id, details){
211 this.errors.push({type, id, details})
212 }
213
214 queueApps(appArray){
215 this.appQueue.push(...appArray)
216 }
217}
218
219export default MonolithicData;