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"
|
|
|
|
|
|
|
|
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]
|
|
|
|
}
|
|
|
|
|
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
|
2022-02-04 14:36:26 +01:00
|
|
|
|
2020-08-26 15:36:04 +02:00
|
|
|
const allStartWith = prefix + key + "-combined"
|
2023-03-08 01:36:27 +01:00
|
|
|
const subOptions = { prefix: "" }
|
2020-08-26 15:36:04 +02:00
|
|
|
// Gives the number of combined preferences
|
2023-03-08 01:36:27 +01:00
|
|
|
const length = this.GetPreference(allStartWith + "-length", "", subOptions)
|
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"
|
2022-09-08 21:40:48 +02:00
|
|
|
)
|
2022-02-04 14:36:26 +01:00
|
|
|
}
|
|
|
|
|
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
|
2023-03-08 01:36:27 +01:00
|
|
|
self.GetPreference(allStartWith + "-" + i, "", subOptions).setData("")
|
2020-10-18 00:28:51 +02:00
|
|
|
}
|
2023-03-08 01:36:27 +01:00
|
|
|
self.GetPreference(allStartWith + "-length", "", subOptions).setData("")
|
2020-10-18 00:28:51 +02:00
|
|
|
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... "
|
|
|
|
}
|
2023-03-08 01:36:27 +01:00
|
|
|
self.GetPreference(allStartWith + "-" + i, "", subOptions).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,
|
2023-03-08 01:36:27 +01:00
|
|
|
options?: {
|
|
|
|
documentation?: string
|
|
|
|
prefix?: string
|
|
|
|
}
|
2022-06-03 01:33:41 +02:00
|
|
|
): UIEventSource<string> {
|
2023-03-08 01:36:27 +01:00
|
|
|
const prefix: string = options?.prefix ?? "mapcomplete-"
|
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-09-08 21:40:48 +02:00
|
|
|
|
2022-06-03 01:33:41 +02:00
|
|
|
const pref = new UIEventSource<string>(
|
|
|
|
this.preferences.data[key] ?? defaultValue,
|
|
|
|
"osm-preference:" + key
|
2022-09-08 21:40:48 +02:00
|
|
|
)
|
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
|
|
|
})
|
|
|
|
|
2023-01-11 03:53:58 +01:00
|
|
|
this.preferences.addCallbackD((allPrefs) => {
|
|
|
|
const v = allPrefs[key]
|
|
|
|
if (v === undefined) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pref.setData(v)
|
|
|
|
})
|
|
|
|
|
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)
|
2023-03-08 01:36:27 +01:00
|
|
|
self.GetPreference(key, "", { prefix: "" }).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",
|
|
|
|
},
|
2022-02-04 14:36:26 +01:00
|
|
|
function (error, value: XMLDocument) {
|
|
|
|
if (error) {
|
|
|
|
console.log("Could not load preferences", error)
|
|
|
|
return
|
|
|
|
}
|
2020-08-26 15:36:04 +02:00
|
|
|
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-09-08 21:40:48 +02:00
|
|
|
}
|
|
|
|
|
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)
|
2022-09-08 21:40:48 +02:00
|
|
|
} else {
|
2022-02-04 14:36:26 +01:00
|
|
|
// OSM does have a value - set it
|
|
|
|
preference.setData(osmValue)
|
2022-09-08 21:40:48 +02:00
|
|
|
}
|
2022-02-04 14:36:26 +01:00
|
|
|
})
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2020-08-26 15:36:04 +02:00
|
|
|
self.preferences.ping()
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
)
|
2020-08-26 15:36:04 +02:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
2023-01-11 03:53:58 +01:00
|
|
|
const self = this
|
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
|
2022-09-08 21:40:48 +02:00
|
|
|
}
|
2023-01-11 03:53:58 +01:00
|
|
|
delete self.preferences.data[k]
|
|
|
|
self.preferences.ping()
|
2021-09-22 16:07:56 +02:00
|
|
|
console.debug("Preference ", k, "removed!")
|
2020-08-26 15:36:04 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
)
|
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-09-08 21:40:48 +02:00
|
|
|
}
|
2023-01-11 03:53:58 +01:00
|
|
|
self.preferences.data[k] = v
|
|
|
|
self.preferences.ping()
|
2022-01-25 00:46:57 +01:00
|
|
|
console.debug(`Preference ${k} written!`)
|
2020-08-26 15:36:04 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
)
|
2020-08-26 15:36:04 +02:00
|
|
|
}
|
|
|
|
}
|