2020-08-26 15:36:04 +02:00
import { UIEventSource } from "../UIEventSource" ;
2021-01-04 04:36:21 +01:00
import UserDetails , { OsmConnection } from "./OsmConnection" ;
2020-08-26 15:36:04 +02:00
import { Utils } from "../../Utils" ;
2022-06-21 18:22:09 +02:00
import { DomEvent } from "leaflet" ;
import preventDefault = DomEvent . preventDefault ;
2020-08-26 15:36:04 +02:00
export class OsmPreferences {
2022-06-21 18:22:09 +02:00
public preferences = new UIEventSource < Record < string , string > > ( { } , "all-osm-preferences" ) ;
2022-02-04 14:36:26 +01:00
private readonly preferenceSources = new Map < string , UIEventSource < string > > ( )
2021-01-15 01:57:46 +01:00
private auth : any ;
private userDetails : UIEventSource < UserDetails > ;
private longPreferences = { } ;
2020-08-26 15:36:04 +02:00
constructor ( auth , osmConnection : OsmConnection ) {
this . auth = auth ;
this . userDetails = osmConnection . userDetails ;
const self = this ;
osmConnection . OnLoggedIn ( ( ) = > self . UpdatePreferences ( ) ) ;
}
/ * *
* OSM preferences can be at most 255 chars
* @param key
* @param prefix
* @constructor
* /
public GetLongPreference ( key : string , prefix : string = "mapcomplete-" ) : UIEventSource < string > {
2020-08-26 20:11:43 +02:00
if ( this . longPreferences [ prefix + key ] !== undefined ) {
return this . longPreferences [ prefix + key ] ;
}
2022-02-04 14:36:26 +01:00
2021-09-09 00:05:51 +02:00
const source = new UIEventSource < string > ( undefined , "long-osm-preference:" + prefix + key ) ;
2020-08-26 20:11:43 +02:00
this . longPreferences [ prefix + key ] = source ;
2020-08-26 15:36:04 +02:00
const allStartWith = prefix + key + "-combined" ;
// Gives the number of combined preferences
2022-06-21 18:22:09 +02:00
const length = this . GetPreference ( allStartWith + "-length" , "" , "" ) ;
2020-08-26 15:36:04 +02:00
2022-02-04 14:36:26 +01:00
if ( ( allStartWith + "-length" ) . length > 255 ) {
throw "This preference key is too long, it has " + key . length + " characters, but at most " + ( 255 - "-length" . length - "-combined" . length - prefix . length ) + " characters are allowed"
}
2020-08-26 15:36:04 +02:00
const self = this ;
source . addCallback ( str = > {
2020-08-26 20:11:43 +02:00
if ( str === undefined || str === "" ) {
2020-10-18 00:28:51 +02:00
return ;
}
2021-01-15 01:57:46 +01:00
if ( str === null ) {
console . error ( "Deleting " + allStartWith ) ;
2020-10-18 00:28:51 +02:00
let count = parseInt ( length . data ) ;
for ( let i = 0 ; i < count ; i ++ ) {
// Delete all the preferences
2022-06-21 18:22:09 +02:00
self . GetPreference ( allStartWith + "-" + i , "" , "" )
2020-10-18 00:28:51 +02:00
. setData ( "" ) ;
}
2022-06-21 18:22:09 +02:00
self . GetPreference ( allStartWith + "-length" , "" , "" )
2020-10-18 00:28:51 +02:00
. setData ( "" ) ;
return
2020-08-26 15:36:04 +02:00
}
let i = 0 ;
while ( str !== "" ) {
2020-08-26 20:11:43 +02:00
if ( str === undefined || str === "undefined" ) {
throw "Long pref became undefined?"
}
if ( i > 100 ) {
throw "This long preference is getting very long... "
}
2022-06-21 18:22:09 +02:00
self . GetPreference ( allStartWith + "-" + i , "" , "" ) . setData ( str . substr ( 0 , 255 ) ) ;
2020-08-26 15:36:04 +02:00
str = str . substr ( 255 ) ;
i ++ ;
}
2020-08-26 20:11:43 +02:00
length . setData ( "" + i ) ; // We use I, the number of preference fields used
2020-08-26 15:36:04 +02:00
} ) ;
function updateData ( l : number ) {
2022-04-13 02:42:33 +02:00
if ( Object . keys ( self . preferences . data ) . length === 0 ) {
// The preferences are still empty - they are not yet updated, so we delay updating for now
return
2020-08-26 15:36:04 +02:00
}
2020-09-15 02:29:31 +02:00
const prefsCount = Number ( l ) ;
if ( prefsCount > 100 ) {
2020-08-26 20:11:43 +02:00
throw "Length to long" ;
}
2020-08-26 15:36:04 +02:00
let str = "" ;
2020-08-26 20:11:43 +02:00
for ( let i = 0 ; i < prefsCount ; i ++ ) {
2022-04-13 02:42:33 +02:00
const key = allStartWith + "-" + i
if ( self . preferences . data [ key ] === undefined ) {
console . warn ( "Detected a broken combined preference:" , key , "is undefined" , self . preferences )
}
str += self . preferences . data [ key ] ? ? "" ;
2020-08-26 15:36:04 +02:00
}
2020-08-26 20:11:43 +02:00
2020-08-26 15:36:04 +02:00
source . setData ( str ) ;
}
length . addCallback ( l = > {
updateData ( Number ( l ) ) ;
} ) ;
2022-04-13 02:42:33 +02:00
this . preferences . addCallbackAndRun ( _ = > {
updateData ( Number ( length . data ) ) ;
} )
2020-08-26 15:36:04 +02:00
return source ;
}
2022-06-03 01:33:41 +02:00
public GetPreference ( key : string , defaultValue : string = undefined , prefix : string = "mapcomplete-" ) : UIEventSource < string > {
2022-06-21 18:22:09 +02:00
if ( key . startsWith ( prefix ) && prefix !== "" ) {
console . trace ( "A preference was requested which has a duplicate prefix in its key. This is probably a bug" )
}
2020-08-26 15:36:04 +02:00
key = prefix + key ;
2021-06-22 14:29:22 +02:00
key = key . replace ( /[:\\\/"' {}.%]/g , '' )
2021-01-15 01:57:46 +01:00
if ( key . length >= 255 ) {
2020-08-26 15:36:04 +02:00
throw "Preferences: key length to big" ;
}
2022-02-04 14:36:26 +01:00
const cached = this . preferenceSources . get ( key )
if ( cached !== undefined ) {
return cached ;
2020-08-26 15:36:04 +02:00
}
if ( this . userDetails . data . loggedIn && this . preferences . data [ key ] === undefined ) {
this . UpdatePreferences ( ) ;
}
2022-02-04 14:36:26 +01:00
2022-06-03 01:33:41 +02:00
const pref = new UIEventSource < string > ( this . preferences . data [ key ] ? ? defaultValue , "osm-preference:" + key ) ;
2020-08-26 15:36:04 +02:00
pref . addCallback ( ( v ) = > {
2022-02-04 14:36:26 +01:00
this . UploadPreference ( key , v ) ;
2020-08-26 15:36:04 +02:00
} ) ;
2022-02-04 14:36:26 +01:00
this . preferenceSources . set ( key , pref )
2020-08-26 15:36:04 +02:00
return pref ;
}
2022-01-26 21:40:38 +01:00
public ClearPreferences() {
let isRunning = false ;
const self = this ;
2022-04-13 02:42:33 +02:00
this . preferences . addCallback ( prefs = > {
console . log ( "Cleaning preferences..." )
2022-01-26 21:40:38 +01:00
if ( Object . keys ( prefs ) . length == 0 ) {
return ;
}
if ( isRunning ) {
return
}
isRunning = true
2022-04-13 02:42:33 +02:00
const prefixes = [ "mapcomplete-" ]
2022-01-26 21:40:38 +01:00
for ( const key in prefs ) {
2022-04-13 02:42:33 +02:00
const matches = prefixes . some ( prefix = > key . startsWith ( prefix ) )
if ( matches ) {
console . log ( "Clearing " , key )
2022-06-21 18:22:09 +02:00
self . GetPreference ( key , "" , "" ) . setData ( "" )
2022-01-26 21:40:38 +01:00
}
}
isRunning = false ;
2022-04-13 02:42:33 +02:00
return ;
2022-01-26 21:40:38 +01:00
} )
}
2020-08-26 15:36:04 +02:00
private UpdatePreferences() {
const self = this ;
this . auth . xhr ( {
method : 'GET' ,
path : '/api/0.6/user/preferences'
} , function ( error , value : XMLDocument ) {
if ( error ) {
console . log ( "Could not load preferences" , error ) ;
return ;
}
const prefs = value . getElementsByTagName ( "preference" ) ;
for ( let i = 0 ; i < prefs . length ; i ++ ) {
const pref = prefs [ i ] ;
const k = pref . getAttribute ( "k" ) ;
const v = pref . getAttribute ( "v" ) ;
self . preferences . data [ k ] = v ;
}
2022-02-04 14:36:26 +01:00
// We merge all the preferences: new keys are uploaded
// For differing values, the server overrides local changes
self . preferenceSources . forEach ( ( preference , key ) = > {
2022-02-14 18:18:05 +01:00
const osmValue = self . preferences . data [ key ]
if ( osmValue === undefined && preference . data !== undefined ) {
2022-02-04 14:36:26 +01:00
// OSM doesn't know this value yet
self . UploadPreference ( key , preference . data )
} else {
// OSM does have a value - set it
preference . setData ( osmValue )
}
} )
2020-08-26 15:36:04 +02:00
self . preferences . ping ( ) ;
} ) ;
}
2022-02-04 14:36:26 +01:00
private UploadPreference ( k : string , v : string ) {
2020-08-26 15:36:04 +02:00
if ( ! this . userDetails . data . loggedIn ) {
2021-09-22 16:07:56 +02:00
console . debug ( ` Not saving preference ${ k } : user not logged in ` ) ;
2020-08-26 15:36:04 +02:00
return ;
}
if ( this . preferences . data [ k ] === v ) {
return ;
}
2021-09-22 16:07:56 +02:00
console . debug ( "Updating preference" , k , " to " , Utils . EllipsesAfter ( v , 15 ) ) ;
2020-08-26 15:36:04 +02:00
2020-08-26 20:11:43 +02:00
if ( v === undefined || v === "" ) {
2020-08-26 15:36:04 +02:00
this . auth . xhr ( {
method : 'DELETE' ,
2021-01-15 01:57:46 +01:00
path : '/api/0.6/user/preferences/' + encodeURIComponent ( k ) ,
2020-08-26 15:36:04 +02:00
options : { header : { 'Content-Type' : 'text/plain' } } ,
2020-08-30 01:13:18 +02:00
} , function ( error ) {
2020-08-26 15:36:04 +02:00
if ( error ) {
2021-09-22 16:07:56 +02:00
console . warn ( "Could not remove preference" , error ) ;
2020-08-26 15:36:04 +02:00
return ;
}
2021-09-22 16:07:56 +02:00
console . debug ( "Preference " , k , "removed!" ) ;
2020-08-26 15:36:04 +02:00
} ) ;
return ;
}
this . auth . xhr ( {
method : 'PUT' ,
2021-01-15 01:57:46 +01:00
path : '/api/0.6/user/preferences/' + encodeURIComponent ( k ) ,
2020-08-26 15:36:04 +02:00
options : { header : { 'Content-Type' : 'text/plain' } } ,
content : v
2020-08-30 01:13:18 +02:00
} , function ( error ) {
2020-08-26 15:36:04 +02:00
if ( error ) {
2022-01-25 00:46:57 +01:00
console . warn ( ` Could not set preference " ${ k } "' ` , error ) ;
2020-08-26 15:36:04 +02:00
return ;
}
2022-01-25 00:46:57 +01:00
console . debug ( ` Preference ${ k } written! ` ) ;
2020-08-26 15:36:04 +02:00
} ) ;
}
}