Compare commits
159 Commits
Author | SHA1 | Date | |
---|---|---|---|
af13bfc920 | |||
e24fd92f85 | |||
7e27cefe6a | |||
450cf6424e | |||
54898d3dbb | |||
dd851a2b25 | |||
4c6b44eb30 | |||
74a3efe78d | |||
51301fc49e | |||
02dd8c3dd0 | |||
26a778c3b2 | |||
9fecbd97e8 | |||
e1383e3903 | |||
47532b8512 | |||
3c4959433a | |||
e921b4a86a | |||
b23b0ca239 | |||
191b45f054 | |||
15d0383349 | |||
d2485583fd | |||
2b94704916 | |||
85ac6c215a | |||
e83e665db9 | |||
645aafef16 | |||
152c893a6f | |||
7c130dda56 | |||
2d82dad806 | |||
e8ac5b759d | |||
4833d18968 | |||
6eafded1f6 | |||
7b440b720e | |||
e20ba7384f | |||
45231c6ede | |||
35475defb5 | |||
8741841f27 | |||
5282d19b55 | |||
d9782aa0fb | |||
9751facfb4 | |||
e0110203e7 | |||
088b44cc2c | |||
8f63bcbfbf | |||
c8029388c9 | |||
d9c4d847a1 | |||
df9d9425ec | |||
90bb3c684e | |||
9c81b6de8a | |||
6383498041 | |||
daeb88785d | |||
dcea08f73b | |||
b252b921f8 | |||
172826bf13 | |||
060f1980f5 | |||
e223d35252 | |||
99dba1a4c6 | |||
b52026c81f | |||
47b8c86426 | |||
2e55c68648 | |||
b7362dd84d | |||
01637b31e1 | |||
0e9a39608a | |||
79404e4d41 | |||
35c21fbdaf | |||
8c7bd7dc11 | |||
09ad4f0320 | |||
d96b836bef | |||
59b2ffaf95 | |||
f1b55ddd64 | |||
85acac3a30 | |||
befff5c1e5 | |||
d72ba81a67 | |||
fef88e2032 | |||
20557e8ce4 | |||
99c905e908 | |||
d7b58ee2c5 | |||
faca2d387b | |||
358d02d97f | |||
b66dac7465 | |||
f7d201859a | |||
61d2ef5469 | |||
ac994b9c62 | |||
264dcbc331 | |||
e5425c0ffb | |||
e10803de68 | |||
07b1a0e403 | |||
6ed2c702d8 | |||
5c1c33d33e | |||
70d37c88b5 | |||
1ba37d95b5 | |||
0d82198849 | |||
39927e75f2 | |||
e6fd33b969 | |||
e8fe32d5af | |||
bfc8bb864d | |||
9179746763 | |||
d0177d24cb | |||
0573008c9c | |||
9506f518c2 | |||
0f0ae9153b | |||
09c7c8ac64 | |||
5e2dfff148 | |||
958b47548d | |||
16155ef746 | |||
5755b61ea6 | |||
353847a77f | |||
bdf64edeb8 | |||
b5768dd927 | |||
3e5abf3a4d | |||
d3029639de | |||
d21d7e4add | |||
afde69b5d9 | |||
3319df3df0 | |||
1102feaac3 | |||
deede728be | |||
fc3dd84122 | |||
9239441d73 | |||
b984811851 | |||
1c52446331 | |||
b6dffa8e66 | |||
315d650d27 | |||
07c121044a | |||
f3169afcf5 | |||
c371fc2a8e | |||
6889e11fd1 | |||
fb73fd0afc | |||
6fcebd7a08 | |||
15ea62a546 | |||
b0cd58f5aa | |||
7fe8f66fd3 | |||
68ca99e9d9 | |||
a2542c658b | |||
eb203c7e62 | |||
6ef466f3ed | |||
5074246462 | |||
73bbcebddb | |||
18128303b6 | |||
c4a2d790a3 | |||
c1ec150696 | |||
f4b856df15 | |||
85b87553dd | |||
5decdf3afa | |||
a4acee4939 | |||
d06aea2831 | |||
ae0a8b0a33 | |||
f0452704a1 | |||
b8b1f1ba80 | |||
caf7478da4 | |||
0e40ba78a4 | |||
d1eac6c9eb | |||
8f5201b2bc | |||
6022001d66 | |||
f018c367ed | |||
48c47f097a | |||
39ac215b5a | |||
7d562ce85c | |||
51b317233a | |||
87ce715011 | |||
ef5afc1e23 | |||
486212f22a | |||
0e8867dd6e |
79
Makefile
79
Makefile
@ -3,6 +3,10 @@
|
||||
MAKEFLAGS += --warn-undefined-variables
|
||||
MAKEFLAGS += --no-builtin-rules
|
||||
|
||||
VERSION_CODE := 11
|
||||
VERSION_NUMBER := 0.0.11
|
||||
VERSION_NAME := Be nothing, and you will have everything to give to others.
|
||||
|
||||
PROJECT = tildefriends
|
||||
BUILD_DIR ?= out
|
||||
BUILD_TYPES := debug release windebug winrelease androiddebug androidrelease androiddebug-x86_64 androidrelease-x86_64
|
||||
@ -24,8 +28,7 @@ ANDROID_SDK ?= ~/Android/Sdk
|
||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/33.0.1
|
||||
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-33
|
||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/23.1.7779620
|
||||
ANDROID_NDK_API_VERSION := 31
|
||||
ANDROID_MIN_SDK_VERSION := 26
|
||||
ANDROID_MIN_SDK_VERSION := 28
|
||||
|
||||
ANDROID_ARM64_TARGETS := \
|
||||
out/androiddebug/tildefriends \
|
||||
@ -58,7 +61,8 @@ $(ANDROID_TARGETS): CFLAGS += \
|
||||
-fPIC \
|
||||
-fdebug-compilation-dir . \
|
||||
-fomit-frame-pointer \
|
||||
-fno-asynchronous-unwind-tables
|
||||
-fno-asynchronous-unwind-tables \
|
||||
-funwind-tables
|
||||
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
|
||||
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
|
||||
$(RELEASE_TARGETS): CFLAGS += -DNDEBUG
|
||||
@ -77,10 +81,10 @@ windebug winrelease: LDFLAGS += \
|
||||
-Ldeps/openssl/mingw64/lib
|
||||
$(ANDROID_X86_64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := x86_64-linux-android
|
||||
$(ANDROID_ARM64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := aarch64-linux-android
|
||||
$(ANDROID_TARGETS): CC = $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/clang
|
||||
$(ANDROID_TARGETS): CC = $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/$(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION)-clang
|
||||
$(ANDROID_TARGETS): AS = $(CC)
|
||||
$(ANDROID_TARGETS): CFLAGS += \
|
||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_NDK_API_VERSION) \
|
||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
||||
-Wno-unknown-warning-option
|
||||
$(ANDROID_ARM64_TARGETS): CFLAGS += -Ideps/openssl/android/arm64-v8a/usr/local/include
|
||||
$(ANDROID_ARM64_TARGETS): LDFLAGS += -Ldeps/openssl/android/arm64-v8a/usr/local/lib
|
||||
@ -117,6 +121,10 @@ $(APP_OBJS): CFLAGS += \
|
||||
-Ideps/xopt \
|
||||
-Wdouble-promotion \
|
||||
-Werror
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
$(filter-out $(BUILD_DIR)/android%,$(APP_OBJS)): CFLAGS += \
|
||||
-fanalyzer
|
||||
endif
|
||||
|
||||
BLOWFISH_SOURCES := \
|
||||
deps/crypt_blowfish/crypt_blowfish.c \
|
||||
@ -243,7 +251,8 @@ SODIUM_SOURCES := \
|
||||
deps/libsodium/src/libsodium/sodium/core.c \
|
||||
deps/libsodium/src/libsodium/sodium/codecs.c \
|
||||
deps/libsodium/src/libsodium/sodium/runtime.c \
|
||||
deps/libsodium/src/libsodium/sodium/utils.c
|
||||
deps/libsodium/src/libsodium/sodium/utils.c \
|
||||
deps/libsodium/src/libsodium/sodium/version.c
|
||||
SODIUM_OBJS := $(call get_objs,SODIUM_SOURCES)
|
||||
$(SODIUM_OBJS): CFLAGS += \
|
||||
-DCONFIGURED=1 \
|
||||
@ -252,6 +261,7 @@ $(SODIUM_OBJS): CFLAGS += \
|
||||
-Wno-unused-variable \
|
||||
-Wno-type-limits \
|
||||
-Wno-unknown-pragmas \
|
||||
-Ideps/libsodium/builds/msvc \
|
||||
-Ideps/libsodium/src/libsodium/include/sodium
|
||||
|
||||
SQLITE_SOURCES := deps/sqlite/sqlite3.c
|
||||
@ -264,7 +274,7 @@ $(SQLITE_OBJS): CFLAGS += \
|
||||
-DSQLITE_ENABLE_FTS5 \
|
||||
-DSQLITE_ENABLE_JSON1 \
|
||||
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
|
||||
-DSQLITE_MAX_ATTACHED=0 \
|
||||
-DSQLITE_MAX_ATTACHED=1 \
|
||||
-DSQLITE_MAX_COLUMN=100 \
|
||||
-DSQLITE_MAX_COMPOUND_SELECT=300 \
|
||||
-DSQLITE_MAX_EXPR_DEPTH=40 \
|
||||
@ -385,7 +395,7 @@ windebug winrelease: LDFLAGS += \
|
||||
-lws2_32 \
|
||||
-lwsock32
|
||||
$(ANDROID_TARGETS): LDFLAGS += \
|
||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_NDK_API_VERSION) \
|
||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
||||
-ldl \
|
||||
-llog \
|
||||
-lssl \
|
||||
@ -432,6 +442,18 @@ endef
|
||||
|
||||
$(foreach build_type,$(BUILD_TYPES),$(eval $(call build_rules,$(build_type))))
|
||||
|
||||
src/version.h : $(firstword $(MAKEFILE_LIST))
|
||||
@echo [version] $@
|
||||
@echo "#define VERSION_NUMBER \"$(VERSION_NUMBER)\"\n#define VERSION_NAME \"$(VERSION_NAME)\"\n" > $@
|
||||
|
||||
src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
|
||||
@echo [android_version] $@
|
||||
@sed -i \
|
||||
-e 's/versionCode=".*"/versionCode="$(VERSION_CODE)"/' \
|
||||
-e 's/versionName=".*"/versionName="$(VERSION_NUMBER)"/' \
|
||||
-e 's/android:minSdkVersion=".*"/android:minSdkVersion="$(ANDROID_MIN_SDK_VERSION)"/' \
|
||||
$@
|
||||
|
||||
# Android support.
|
||||
out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
|
||||
@mkdir -p $(dir $@)
|
||||
@ -463,9 +485,7 @@ PACKAGE_DIRS := \
|
||||
apps/ \
|
||||
core/ \
|
||||
deps/codemirror/ \
|
||||
deps/lit/ \
|
||||
deps/split/ \
|
||||
deps/smoothie/
|
||||
deps/lit/
|
||||
|
||||
RAW_FILES := $(shell find $(PACKAGE_DIRS) -type f)
|
||||
|
||||
@ -491,10 +511,10 @@ out/%.apk: out/apk/%.unsigned.apk
|
||||
@echo [apksigner] $(notdir $@)
|
||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks keystore.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --out $@ $<
|
||||
|
||||
apk: out/TildeFriends-debug.apk
|
||||
apk: out/TildeFriends-release.apk
|
||||
.PHONY: apk
|
||||
|
||||
apkgo: out/TildeFriends-debug.apk
|
||||
apkgo: out/TildeFriends-release.apk
|
||||
@adb install $<
|
||||
@adb shell am start com.unprompted.tildefriends/.MainActivity
|
||||
.PHONY: apkgo
|
||||
@ -506,3 +526,36 @@ apklog:
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
.PHONY: clean
|
||||
|
||||
dist: apk
|
||||
@echo "[export] $$(svn info --show-item url)"
|
||||
@rm -rf tildefriends-$(VERSION_NUMBER)
|
||||
@svn export -q . tildefriends-$(VERSION_NUMBER)
|
||||
@echo "tildefriends-$(VERSION_NUMBER): $(VERSION_NAME)" > tildefriends-$(VERSION_NUMBER)/VERSION
|
||||
@echo "[tar] tildefriends-$(VERSION_NUMBER).tar.xz"
|
||||
@tar \
|
||||
--exclude=apps/gg* \
|
||||
--exclude=deps/libbacktrace/Isaac.Newton-Opticks.txt \
|
||||
--exclude=deps/libsodium/builds/msvc/vs* \
|
||||
--exclude=deps/libsodium/builds/msvc/build \
|
||||
--exclude=deps/libsodium/builds/msvc/properties \
|
||||
--exclude=deps/libsodium/configure \
|
||||
--exclude=deps/libsodium/test \
|
||||
--exclude=deps/libuv/docs \
|
||||
--exclude=deps/libuv/test \
|
||||
--exclude=deps/openssl \
|
||||
--exclude=deps/speedscope/*.map \
|
||||
--exclude=deps/sqlite/shell.c \
|
||||
--exclude=deps/zlib/contrib/vstudio \
|
||||
--exclude=deps/zlib/doc \
|
||||
-caf tildefriends-$(VERSION_NUMBER).tar.xz tildefriends-$(VERSION_NUMBER)
|
||||
@rm -rf tildefriends-$(VERSION_NUMBER)
|
||||
@echo "[cp] TildeFriends-$(VERSION_NUMBER).apk"
|
||||
@cp out/TildeFriends-release.apk TildeFriends-$(VERSION_NUMBER).apk
|
||||
.PHONY: dist
|
||||
|
||||
dist-test: dist
|
||||
@tar -xf tildefriends-$(VERSION_NUMBER).tar.xz
|
||||
@$(MAKE) -C tildefriends-$(VERSION_NUMBER)/ debug release
|
||||
@rm -rf tildefriends-$(VERSION_NUMBER)
|
||||
.PHONY: dist-test
|
||||
|
@ -9,14 +9,18 @@ tfrpc.register(function global_settings_set(key, value) {
|
||||
});
|
||||
|
||||
async function main() {
|
||||
let data = {
|
||||
users: {},
|
||||
granted: await core.allPermissionsGranted(),
|
||||
settings: await core.globalSettingsDescriptions(),
|
||||
};
|
||||
for (let user of await core.users()) {
|
||||
data.users[user] = await core.permissionsForUser(user);
|
||||
try {
|
||||
let data = {
|
||||
users: {},
|
||||
granted: await core.allPermissionsGranted(),
|
||||
settings: await core.globalSettingsDescriptions(),
|
||||
};
|
||||
for (let user of await core.users()) {
|
||||
data.users[user] = await core.permissionsForUser(user);
|
||||
}
|
||||
await app.setDocument(utf8Decode(getFile('index.html')).replace('$data', JSON.stringify(data)));
|
||||
} catch {
|
||||
await app.setDocument('<span style="color: #f00">Only an administrator can modify these settings.</span>');
|
||||
}
|
||||
await app.setDocument(utf8Decode(getFile('index.html')).replace('$data', JSON.stringify(data)));
|
||||
}
|
||||
main();
|
@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html style="width: 100%">
|
||||
<head>
|
||||
<script>const g_data = $data;</script>
|
||||
</head>
|
||||
<body style="color: #fff">
|
||||
<body style="color: #fff; width: 100%">
|
||||
<h1>Tilde Friends Administration</h1>
|
||||
</body>
|
||||
<script type="module" src="script.js"></script>
|
||||
|
@ -25,29 +25,37 @@ window.addEventListener('load', function() {
|
||||
function input_template(key, description) {
|
||||
if (description.type === 'boolean') {
|
||||
return html`
|
||||
<label ?for=${'gs_' + key} style="grid-column: 1">${key}: </label>
|
||||
<input type="checkbox" ?checked=${description.value} ?id=${'gs_' + key} style="grid-column: 2"></input>
|
||||
<div style="grid-column: 3">
|
||||
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.checked)}>Set</button>
|
||||
<span>${description.description}</span>
|
||||
<div style="margin-top: 1em">
|
||||
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
|
||||
<div>
|
||||
<input type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input>
|
||||
<button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.checked)}>Set</button>
|
||||
<div>${description.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else if (description.type === 'textarea') {
|
||||
return html`
|
||||
<label ?for=${'gs_' + key} style="grid-column: 1">${key}: </label>
|
||||
<textarea style="vertical-align: top" rows=20 cols=80 ?id=${'gs_' + key}>${description.value}</textarea>
|
||||
<div style="grid-column: 3">
|
||||
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.value)}>Set</button>
|
||||
<span>${description.description}</span>
|
||||
<div style="margin-top: 1em"">
|
||||
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
|
||||
<div style="width: 100%; padding: 0; margin: 0">
|
||||
<div style="width: 90%; padding: 0 margin: 0">
|
||||
<textarea style="vertical-align: top; width: 100%" rows=20 cols=80 id=${'gs_' + key}>${description.value}</textarea>
|
||||
</div>
|
||||
<button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.firstElementChild.value)}>Set</button>
|
||||
<div>${description.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
return html`
|
||||
<label ?for=${'gs_' + key} style="grid-column: 1">${key}: </label>
|
||||
<input type="text" value="${description.value}" ?id=${'gs_' + key}></input>
|
||||
<div style="grid-column: 3">
|
||||
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.value)}>Set</button>
|
||||
<span>${description.description}</span>
|
||||
<div style="margin-top: 1em">
|
||||
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
|
||||
<div>
|
||||
<input type="text" value="${description.value}" id=${'gs_' + key}></input>
|
||||
<button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>
|
||||
<div>${description.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -67,12 +75,13 @@ window.addEventListener('load', function() {
|
||||
${Object.entries(users).map(u => user_template(u[0], u[1]))}
|
||||
</ul>`;
|
||||
const page_template = (data) =>
|
||||
html`<div>
|
||||
<h2>Global Settings</h2>
|
||||
<div style="display: grid">
|
||||
${Object.keys(data.settings).sort().map(x => html`${input_template(x, data.settings[x])}`)}
|
||||
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
|
||||
<h2>Global Settings</h2>
|
||||
<div>
|
||||
${Object.keys(data.settings).sort().map(x => html`${input_template(x, data.settings[x])}`)}
|
||||
</div>
|
||||
${users_template(data.users)}
|
||||
</div>
|
||||
${users_template(data.users)}
|
||||
</div>`;
|
||||
`;
|
||||
render(page_template(g_data), document.body);
|
||||
});
|
File diff suppressed because one or more lines are too long
313
apps/api/docs.js
Normal file
313
apps/api/docs.js
Normal file
@ -0,0 +1,313 @@
|
||||
export const docs = {};
|
||||
|
||||
docs.global = `# Tilde Friends API Documentation
|
||||
|
||||
Welcome to the Tilde Friends API documentation.
|
||||
|
||||
* [App Globals](#App_Globals)
|
||||
* [Database Interface](#Database)
|
||||
* [Remote Procedure Calls](#tfrpc)
|
||||
|
||||
<a id="App_Globals"></a>
|
||||
## <span style="color: #aaf">App Globals</span>
|
||||
The following are functions and values exposed to all apps in their \`app.js\` or \`handler.js\`. Most
|
||||
of these are asynchronous, returning a \`Promise\` that will be resolved when the call completes, unless
|
||||
noted otherwise.
|
||||
|
||||
This is all a work in progess. These are liable to change without warning. Feedback is welcome.
|
||||
|
||||
The exposed functions in this API balance multiple competing needs:
|
||||
* The surface area of the exposed API ought to be fairly minimal. If something can be implemented entirely app-side, that is
|
||||
generally preferred over building it into the core.
|
||||
* Everything is built on this API. Ideally the admin app, the SSB app, and the editor all use standard API exposed to all
|
||||
apps, with appropriate permission guards in place making it so that only trusted apps do potentially destructive operations.
|
||||
There will be some things here that aren't necessarily general use to support what's required.
|
||||
|
||||
If you are looking at the [Tilde Friends source code](https://www.tildefriends.net/~cory/releases/),
|
||||
the vast majority of these are implemented in \`src/*.js.c\` files, and exposed to apps via \`core/core.js\`.
|
||||
`;
|
||||
|
||||
docs['core.user.credentials.session.name'] = `
|
||||
*String* The name of the authenticated user.
|
||||
`;
|
||||
|
||||
docs['app.setDocument()'] = `
|
||||
Set the contents of the client <iframe/>.
|
||||
### Parameters
|
||||
* *String* **html** The HTML contents.
|
||||
`;
|
||||
|
||||
docs['ssb.sqlAsync()'] = `
|
||||
Run an SQL query against the sqlite database.
|
||||
### Parameters
|
||||
* *String* **query** The sqlite query.
|
||||
* *Array* **args** The query arguments to bind.
|
||||
* *Function* **callback** Callback called for each row result.
|
||||
`;
|
||||
|
||||
docs['ssb.appendMessageWithIdentity()'] = `
|
||||
Signs and stores a message in the SSB database.
|
||||
### Parameters
|
||||
* *String* **id** The public key of an SSB identity owned by the authenticated user.
|
||||
* *Object* **message** The unsigned message.
|
||||
`;
|
||||
|
||||
docs['ssb.storeMessage()'] = `
|
||||
Verifies and stores a signed message in the SSB database.
|
||||
### Parameters
|
||||
* *Object* **message** The valid, signed message to store.
|
||||
`;
|
||||
|
||||
docs['ssb.blobStore()'] = `
|
||||
Store a blob in the SSB database.
|
||||
### Parameters
|
||||
* *String*/*Uint8Array* **blob** The blob contents to store
|
||||
### Returns
|
||||
*String* The stored blob ID.
|
||||
`;
|
||||
|
||||
docs['ssb.blobGet()'] = `
|
||||
Fetches a blob from the database.
|
||||
### Parameters
|
||||
* *String* **blob_id** The blob identifier to fetch (\`&....sha256\`).
|
||||
### Returns
|
||||
*ArrayBuffer* The blob data.
|
||||
`;
|
||||
|
||||
docs['print()'] = `
|
||||
Log debug information both to the server's console and to the visiting user's browser console when possible.
|
||||
### Parameters
|
||||
* **...** Whatever you want to log. Will be joined with spaces.
|
||||
`;
|
||||
|
||||
docs['database()'] = `
|
||||
Returns a database instance that is specific to the authenticated user and the given key.
|
||||
### Parameters
|
||||
* *String* **key** The database key.
|
||||
### Returns
|
||||
*Database* A database.
|
||||
`;
|
||||
|
||||
docs['my_shared_database()'] = `
|
||||
Returns a database instance that is specific to the authenticated user and the given key.
|
||||
### Parameters
|
||||
* *String* **package_name** The database package name.
|
||||
* *String* **key** The database key.
|
||||
### Returns
|
||||
*Database* A database.
|
||||
`;
|
||||
|
||||
docs['shared_database()'] = `
|
||||
Returns a database instance that is shared between all users of the app, determined by its owner and app name.
|
||||
### Parameters
|
||||
* *String* **key** The database key.
|
||||
### Returns
|
||||
*Database* A database.
|
||||
`;
|
||||
|
||||
docs['base64Decode()'] = `
|
||||
Decode a base64 string to bytes.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *String* value The base64-encoded string.
|
||||
### Returns
|
||||
*Uint8Array* The decoded bytes.
|
||||
`;
|
||||
|
||||
docs['base64Encode()'] = `
|
||||
Encode bytes to a base64 string.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *Uint8Array* The bytes to encode.
|
||||
### Returns
|
||||
*String* The base64-encoded string.
|
||||
`;
|
||||
|
||||
docs['utf8Decode()'] = `
|
||||
Decode UTF-8 bytes to a string.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *Uint8Array* **value** The value to decode.
|
||||
### Returns
|
||||
*String* The value as a string.
|
||||
`;
|
||||
|
||||
docs['utf8Encode()'] = `
|
||||
Encodes a string to UTF-8 bytes.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *String* **value** The value to encode.
|
||||
### Returns
|
||||
*Uint8Array* The encoded \`value\`.
|
||||
`;
|
||||
|
||||
docs['setTimeout()'] = `
|
||||
Call a function after some delay.
|
||||
### Parameters
|
||||
* *Function* **callback** The function to call.
|
||||
* *Number* **timeout** Number of milliseconds to wait before calling the callback function.
|
||||
`;
|
||||
|
||||
docs['parseHttpRequest()'] = `
|
||||
Parses an HTTP request.
|
||||
### Parameters
|
||||
* *Uint8Array* **request** The request data. Maybe be partial or contain extra data. The return value will
|
||||
indicate when and where it is complete.
|
||||
* *Number* **last_length** The length of the data passed on a previous attempt for the same request, or 0 initially.
|
||||
### Returns
|
||||
* *Integer* **-2** if the request is incomplete.
|
||||
* *Integer* **-1** if the request could not be parsed.
|
||||
* *Object* An object with **bytes_parsed**, **minor_version**, **path**, and **headers** fields on successful parse.
|
||||
`;
|
||||
|
||||
docs['parseHttpResponse()'] = `
|
||||
Parses an HTTP response.
|
||||
### Parameters
|
||||
* *Uint8Array* **response** The response data. Maybe be partial or contain extra data. The return value will
|
||||
indicate when and where it is complete.
|
||||
* *Number* **last_length** The length of the data passed on a previous attempt for the same response, or 0 initially.
|
||||
### Returns
|
||||
* *Integer* **-2** if the response is incomplete.
|
||||
* *Integer* **-1** if the response could not be parsed.
|
||||
* *Object* An object with **bytes_parsed**, **minor_version**, **status**, **message**, and **headers** fields on successful parse.
|
||||
`;
|
||||
|
||||
docs['sha1Digest()'] =`
|
||||
Calculates a SHA1 digest.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *String* **value** The value for which to calculate the digest.
|
||||
### Returns
|
||||
*String* The SHA1 digest of UTF-8 encoded \`value\`.
|
||||
`;
|
||||
|
||||
docs['maskBytes()'] = `
|
||||
Masks bytes for WebSocket communication.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *Uint8Array* **bytes** The byte array of data to mask.
|
||||
* *Uint32* **mask** The mask to apply.
|
||||
### Returns
|
||||
*Uint32Array* The masked bytes.
|
||||
`;
|
||||
|
||||
docs['exit()'] = `
|
||||
Exits the app. But why would you want to do that?
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *Integer* **exit_code** System exit code.
|
||||
`;
|
||||
|
||||
docs['version()'] = `
|
||||
Gets version information for the running server.
|
||||
### Returns
|
||||
*Object* Keys are things like \`name\` and \`number\` for the server itself and \`libuv\` and \`openssl\` for
|
||||
dependencies. Values are *String* version numbers.
|
||||
`;
|
||||
|
||||
docs['platform()'] = `
|
||||
Gets the host operating system platform of the running server.
|
||||
### Returns
|
||||
*String* The platform, one of \`windows\`, \`android\`, \`linux\`, or \`other\`.
|
||||
`;
|
||||
|
||||
docs['getFile()'] = `
|
||||
Gets a file from the running app.
|
||||
### Parameters
|
||||
* *String* **name** Name of the file to retrieve.
|
||||
### Returns
|
||||
*Uint8Array* The contents of a file from the app with the given name, or *undefined*.
|
||||
`;
|
||||
|
||||
docs.database = `
|
||||
# <span style="color: #aaf">Database</span>
|
||||
Local-only storage is provided by a \`Database\` type representing a key-value store.
|
||||
`;
|
||||
|
||||
docs['database.get()'] = `
|
||||
Gets a value from the database.
|
||||
### Parameters
|
||||
* *String* **key** The key.
|
||||
### Returns
|
||||
*String* The value from the database or undefined if not found.
|
||||
`;
|
||||
|
||||
docs['database.getAll()'] = `
|
||||
Gets all keys from the database.
|
||||
### Returns
|
||||
*Array* An array of *String* key names for all keys in the given database.
|
||||
`;
|
||||
|
||||
docs['database.getLike()'] = `
|
||||
Gets all keys and values from the database matching a pattern.
|
||||
### Parameters
|
||||
* *String* **pattern** An sqlite \`LIKE\` pattern to match keys against.
|
||||
### Returns
|
||||
*Object* An object whose keys are the database keys and values are the database values that match the given pattern.
|
||||
`;
|
||||
|
||||
docs['database.set()'] = `
|
||||
Sets a value in the database, creating a new entry or replacing an existing entry.
|
||||
### Parameters
|
||||
* *String* **key** The key.
|
||||
* *String* **value** The value.
|
||||
`;
|
||||
|
||||
docs['database.exchange()'] = `
|
||||
Performs an atomic compare and exchange operation, setting a value in the database only if its current value matches what is expected.
|
||||
### Parameters
|
||||
* *String* **key** The key.
|
||||
* *String* **expected** The expected value.
|
||||
* *String* **value** The new value.
|
||||
### Returns
|
||||
*Boolean* true if the value is now the given value.
|
||||
`;
|
||||
|
||||
docs['database.remove()'] = `
|
||||
Removes an entry from the database if it exists.
|
||||
### Parameters
|
||||
* *String* **key** The key.
|
||||
`;
|
||||
|
||||
docs.tfrpc = `
|
||||
# <span style="color: #aaf" id="tfrpc">tfrpc</span>
|
||||
\`tfrpc.js\` is a small helper script that is available to be used to facilitate communication between parts of an application.
|
||||
|
||||
\`tfrpc.js\` can be used to asynchronously make calls between the app code running in a sandboxed iframe in the browser
|
||||
and the app process on the server.
|
||||
|
||||
From \`app.js\`:
|
||||
\`\`\`
|
||||
import * as tfrpc from '/tfrpc.js';
|
||||
\`\`\`
|
||||
|
||||
|
||||
From script running in the browser:
|
||||
\`\`\`
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
\`\`\`
|
||||
|
||||
Either side can register or call functions, though they must be registered before they can be called. Arguments and return
|
||||
values are ultimately serialized by means that attempt to preserve most JSON-serializable values as well as functions themselves.
|
||||
`;
|
||||
|
||||
docs['tfrpc.register()'] = `
|
||||
Register a function, allowing it to be called remotely.
|
||||
### Parameters
|
||||
* *Function* **function** The function to register. Its name will be how it will be called.
|
||||
`;
|
||||
|
||||
docs['tfrpc.rpc.*()'] = `
|
||||
Call a remote function.
|
||||
### Parameters
|
||||
* **...** Parameters to pass to the function.
|
||||
### Returns
|
||||
The return value of the called function.
|
||||
`;
|
@ -1,73 +1,163 @@
|
||||
var g_following_cache = {};
|
||||
var g_following_deep_cache = {};
|
||||
var g_about_cache = {};
|
||||
let g_about_cache = {};
|
||||
|
||||
async function following(db, id) {
|
||||
if (g_following_cache[id]) {
|
||||
return g_following_cache[id];
|
||||
}
|
||||
var o = await db.get(id + ":following");
|
||||
const k_version = 5;
|
||||
var f = o ? JSON.parse(o) : o;
|
||||
if (!f || f.version != k_version) {
|
||||
f = {users: [], sequence: 0, version: k_version};
|
||||
}
|
||||
f.users = new Set(f.users);
|
||||
await ssb.sqlAsync(
|
||||
"SELECT "+
|
||||
" sequence, "+
|
||||
" json_extract(content, '$.contact') AS contact, "+
|
||||
" json_extract(content, '$.following') AS following "+
|
||||
"FROM messages "+
|
||||
"WHERE "+
|
||||
" author = ?1 AND "+
|
||||
" sequence > ?2 AND "+
|
||||
" json_extract(content, '$.type') = 'contact' "+
|
||||
"UNION SELECT MAX(sequence) AS sequence, NULL, NULL FROM messages WHERE author = ?1 "+
|
||||
"ORDER BY sequence",
|
||||
[id, f.sequence],
|
||||
function(row) {
|
||||
if (row.following) {
|
||||
f.users.add(row.contact);
|
||||
} else {
|
||||
f.users.delete(row.contact);
|
||||
}
|
||||
f.sequence = row.sequence;
|
||||
});
|
||||
var as_set = f.users;
|
||||
f.users = Array.from(f.users).sort();
|
||||
var j = JSON.stringify(f);
|
||||
if (o != j) {
|
||||
await db.set(id + ":following", j);
|
||||
}
|
||||
f.users = as_set;
|
||||
g_following_cache[id] = f.users;
|
||||
return f.users;
|
||||
async function query(sql, args) {
|
||||
let result = [];
|
||||
await ssb.sqlAsync(sql, args, function(row) {
|
||||
result.push(row);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
async function followingDeep(db, seed_ids, depth) {
|
||||
if (depth <= 0) {
|
||||
return seed_ids;
|
||||
async function contacts_internal(id, last_row_id, following, max_row_id) {
|
||||
let result = Object.assign({}, following[id] || {});
|
||||
result.following = result.following || {};
|
||||
result.blocking = result.blocking || {};
|
||||
let contacts = await query(
|
||||
`
|
||||
SELECT content FROM messages
|
||||
WHERE author = ? AND
|
||||
rowid > ? AND
|
||||
rowid <= ? AND
|
||||
json_extract(content, '$.type') = 'contact'
|
||||
ORDER BY sequence
|
||||
`,
|
||||
[id, last_row_id, max_row_id]);
|
||||
for (let row of contacts) {
|
||||
let contact = JSON.parse(row.content);
|
||||
if (contact.following === true) {
|
||||
result.following[contact.contact] = true;
|
||||
} else if (contact.following === false) {
|
||||
delete result.following[contact.contact];
|
||||
} else if (contact.blocking === true) {
|
||||
result.blocking[contact.contact] = true;
|
||||
} else if (contact.blocking === false) {
|
||||
delete result.blocking[contact.contact];
|
||||
}
|
||||
}
|
||||
var key = JSON.stringify([seed_ids, depth]);
|
||||
if (g_following_deep_cache[key]) {
|
||||
return g_following_deep_cache[key];
|
||||
following[id] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
async function contact(id, last_row_id, following, max_row_id) {
|
||||
return await contacts_internal(id, last_row_id, following, max_row_id);
|
||||
}
|
||||
|
||||
async function following_deep_internal(ids, depth, blocking, last_row_id, following, max_row_id) {
|
||||
let contacts = await Promise.all([...new Set(ids)].map(x => contact(x, last_row_id, following, max_row_id)));
|
||||
let result = {};
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
let id = ids[i];
|
||||
let contact = contacts[i];
|
||||
let all_blocking = Object.assign({}, contact.blocking, blocking);
|
||||
let found = Object.keys(contact.following).filter(y => !all_blocking[y]);
|
||||
let deeper = depth > 1 ? await following_deep_internal(found, depth - 1, all_blocking, last_row_id, following, max_row_id) : [];
|
||||
result[id] = [id, ...found, ...deeper];
|
||||
}
|
||||
var f = await Promise.all(seed_ids.map(x => following(db, x).then(x => [...x])));
|
||||
var ids = [].concat(...f);
|
||||
var x = await followingDeep(db, [...new Set(ids)].sort(), depth - 1);
|
||||
x = [...new Set([].concat(...x, ...seed_ids))].sort();
|
||||
g_following_deep_cache[key] = x;
|
||||
return x;
|
||||
return [...new Set(Object.values(result).flat())];
|
||||
}
|
||||
|
||||
async function following_deep(ids, depth, blocking) {
|
||||
let db = await database('cache');
|
||||
const k_cache_version = 5;
|
||||
let cache = await db.get('following');
|
||||
cache = cache ? JSON.parse(cache) : {};
|
||||
if (cache.version !== k_cache_version) {
|
||||
cache = {
|
||||
version: k_cache_version,
|
||||
following: {},
|
||||
last_row_id: 0,
|
||||
};
|
||||
}
|
||||
let max_row_id = (await query(`
|
||||
SELECT MAX(rowid) AS max_row_id FROM messages
|
||||
`, []))[0].max_row_id;
|
||||
let result = await following_deep_internal(ids, depth, blocking, cache.last_row_id, cache.following, max_row_id);
|
||||
cache.last_row_id = max_row_id;
|
||||
let store = JSON.stringify(cache);
|
||||
await db.set('following', store);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function fetch_about(db, ids, users) {
|
||||
const k_cache_version = 1;
|
||||
let cache = await db.get('about');
|
||||
cache = cache ? JSON.parse(cache) : {};
|
||||
if (cache.version !== k_cache_version) {
|
||||
cache = {
|
||||
version: k_cache_version,
|
||||
about: {},
|
||||
last_row_id: 0,
|
||||
};
|
||||
}
|
||||
let max_row_id = 0;
|
||||
await ssb.sqlAsync(`
|
||||
SELECT MAX(rowid) AS max_row_id FROM messages
|
||||
`,
|
||||
[],
|
||||
function(row) {
|
||||
max_row_id = row.max_row_id;
|
||||
});
|
||||
for (let id of Object.keys(cache.about)) {
|
||||
if (ids.indexOf(id) == -1) {
|
||||
delete cache.about[id];
|
||||
}
|
||||
}
|
||||
|
||||
let abouts = [];
|
||||
await ssb.sqlAsync(
|
||||
`
|
||||
SELECT
|
||||
messages.*
|
||||
FROM
|
||||
messages,
|
||||
json_each(?1) AS following
|
||||
WHERE
|
||||
messages.author = following.value AND
|
||||
messages.rowid > ?3 AND
|
||||
messages.rowid <= ?4 AND
|
||||
json_extract(messages.content, '$.type') = 'about'
|
||||
UNION
|
||||
SELECT
|
||||
messages.*
|
||||
FROM
|
||||
messages,
|
||||
json_each(?2) AS following
|
||||
WHERE
|
||||
messages.author = following.value AND
|
||||
messages.rowid <= ?4 AND
|
||||
json_extract(messages.content, '$.type') = 'about'
|
||||
ORDER BY messages.author, messages.sequence
|
||||
`,
|
||||
[
|
||||
JSON.stringify(ids.filter(id => cache.about[id])),
|
||||
JSON.stringify(ids.filter(id => !cache.about[id])),
|
||||
cache.last_row_id,
|
||||
max_row_id,
|
||||
]);
|
||||
for (let about of abouts) {
|
||||
let content = JSON.parse(about.content);
|
||||
if (content.about === about.author) {
|
||||
delete content.type;
|
||||
delete content.about;
|
||||
cache.about[about.author] = Object.assign(cache.about[about.author] || {}, content);
|
||||
}
|
||||
}
|
||||
cache.last_row_id = max_row_id;
|
||||
await db.set('about', JSON.stringify(cache));
|
||||
users = users || {};
|
||||
for (let id of Object.keys(cache.about)) {
|
||||
users[id] = Object.assign(users[id] || {}, cache.about[id]);
|
||||
}
|
||||
return Object.assign({}, users);
|
||||
}
|
||||
|
||||
async function getAbout(db, id) {
|
||||
if (g_about_cache[id]) {
|
||||
return g_about_cache[id];
|
||||
}
|
||||
var o = await db.get(id + ":about");
|
||||
let o = await db.get(id + ":about");
|
||||
const k_version = 4;
|
||||
var f = o ? JSON.parse(o) : o;
|
||||
let f = o ? JSON.parse(o) : o;
|
||||
if (!f || f.version != k_version) {
|
||||
f = {about: {}, sequence: 0, version: k_version};
|
||||
}
|
||||
@ -87,7 +177,7 @@ async function getAbout(db, id) {
|
||||
function(row) {
|
||||
f.sequence = row.sequence;
|
||||
if (row.content) {
|
||||
var about = {};
|
||||
let about = {};
|
||||
try {
|
||||
about = JSON.parse(row.content);
|
||||
} catch {
|
||||
@ -97,7 +187,7 @@ async function getAbout(db, id) {
|
||||
f.about = Object.assign(f.about, about);
|
||||
}
|
||||
});
|
||||
var j = JSON.stringify(f);
|
||||
let j = JSON.stringify(f);
|
||||
if (o != j) {
|
||||
await db.set(id + ":about", j);
|
||||
}
|
||||
@ -108,7 +198,7 @@ async function getAbout(db, id) {
|
||||
async function getSize(db, id) {
|
||||
let size = 0;
|
||||
await ssb.sqlAsync(
|
||||
"SELECT (SUM(LENGTH(content)) + SUM(LENGTH(author)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1",
|
||||
"SELECT (LENGTH(author) * COUNT(*) + SUM(LENGTH(content)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1",
|
||||
[id],
|
||||
function (row) {
|
||||
size += row.size;
|
||||
@ -116,6 +206,25 @@ async function getSize(db, id) {
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
async function getSizes(ids) {
|
||||
let sizes = {};
|
||||
await ssb.sqlAsync(
|
||||
`
|
||||
SELECT
|
||||
author,
|
||||
(SUM(LENGTH(content)) + SUM(LENGTH(author)) + SUM(LENGTH(messages.id))) AS size
|
||||
FROM messages
|
||||
JOIN json_each(?) AS ids ON author = ids.value
|
||||
GROUP BY author
|
||||
`,
|
||||
[JSON.stringify(ids)],
|
||||
function (row) {
|
||||
sizes[row.author] = row.size;
|
||||
});
|
||||
return sizes;
|
||||
}
|
||||
|
||||
function niceSize(bytes) {
|
||||
let value = bytes;
|
||||
let unit = 'B';
|
||||
@ -131,27 +240,28 @@ function niceSize(bytes) {
|
||||
return Math.round(value * 10) / 10 + ' ' + unit;
|
||||
}
|
||||
|
||||
async function buildTree(db, root, indent, depth) {
|
||||
var f = await following(db, root);
|
||||
var result = indent + '[' + f.size + '] ' + '<a target="_top" href="../index/#' + root + '">' + ((await getAbout(db, root)).name || root) + '</a> ' + niceSize(await getSize(db, root)) + '\n';
|
||||
if (depth > 0) {
|
||||
for (let next of f) {
|
||||
result += await buildTree(db, next, indent + ' ', depth - 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
function escape(value) {
|
||||
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await app.setDocument('<pre style="color: #fff">building...</pre>');
|
||||
var db = await database('ssb');
|
||||
var whoami = await ssb.getIdentities();
|
||||
var tree = '';
|
||||
for (let id of whoami) {
|
||||
await app.setDocument(`<pre style="color: #fff">building... ${id}</pre>`);
|
||||
tree += await buildTree(db, id, '', 2);
|
||||
let db = await database('ssb');
|
||||
let whoami = await ssb.getIdentities();
|
||||
let tree = '';
|
||||
await app.setDocument(`<pre style="color: #fff">Enumerating followed users...</pre>`);
|
||||
let following = await following_deep(whoami, 2, {});
|
||||
await app.setDocument(`<pre style="color: #fff">Getting names and sizes...</pre>`);
|
||||
let [about, sizes] = await Promise.all([
|
||||
fetch_about(db, following, {}),
|
||||
getSizes(following),
|
||||
]);
|
||||
await app.setDocument(`<pre style="color: #fff">Finishing...</pre>`);
|
||||
following.sort((a, b) => ((sizes[b] ?? 0) - (sizes[a] ?? 0)));
|
||||
for (let id of following) {
|
||||
tree += `<li><a href="/~core/ssb/#${id}">${escape(about[id]?.name ?? id)}</a> ${niceSize(sizes[id] ?? 0)}</li>\n`;
|
||||
}
|
||||
await app.setDocument('<pre style="color: #fff">FOLLOWING:\n' + tree + '</pre>');
|
||||
await app.setDocument('<!DOCTYPE html>\n<html>\n<body style="color: #fff"><h1>Following</h1>\n<ul>' + tree + '</ul>\n</body>\n</html>');
|
||||
}
|
||||
|
||||
main();
|
4
apps/gg.json
Normal file
4
apps/gg.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🗺"
|
||||
}
|
80
apps/gg/app.js
Normal file
80
apps/gg/app.js
Normal file
@ -0,0 +1,80 @@
|
||||
import * as tfrpc from '/tfrpc.js';
|
||||
import * as strava from './strava.js';
|
||||
|
||||
let g_database;
|
||||
let g_shared_database;
|
||||
|
||||
tfrpc.register(async function createIdentity() {
|
||||
return ssb.createIdentity();
|
||||
});
|
||||
tfrpc.register(async function appendMessage(id, message) {
|
||||
print('APPEND', JSON.stringify(message));
|
||||
return ssb.appendMessageWithIdentity(id, message);
|
||||
});
|
||||
tfrpc.register(function url() {
|
||||
return core.url;
|
||||
});
|
||||
tfrpc.register(async function getUser() {
|
||||
return core.user;
|
||||
});
|
||||
tfrpc.register(function getIdentities() {
|
||||
return ssb.getIdentities();
|
||||
});
|
||||
tfrpc.register(async function databaseGet(key) {
|
||||
return g_database ? g_database.get(key) : undefined;
|
||||
});
|
||||
tfrpc.register(async function databaseSet(key, value) {
|
||||
return g_database ? g_database.set(key, value) : undefined;
|
||||
});
|
||||
tfrpc.register(async function databaseRemove(key, value) {
|
||||
return g_database ? g_database.remove(key, value) : undefined;
|
||||
});
|
||||
tfrpc.register(async function sharedDatabaseGet(key) {
|
||||
return g_shared_database ? g_shared_database.get(key) : undefined;
|
||||
});
|
||||
tfrpc.register(async function sharedDatabaseSet(key, value) {
|
||||
return g_shared_database ? g_shared_database.set(key, value) : undefined;
|
||||
});
|
||||
tfrpc.register(async function sharedDatabaseRemove(key, value) {
|
||||
return g_shared_database ? g_shared_database.remove(key, value) : undefined;
|
||||
});
|
||||
tfrpc.register(async function query(sql, args) {
|
||||
let result = [];
|
||||
await ssb.sqlAsync(sql, args, function callback(row) {
|
||||
result.push(row);
|
||||
});
|
||||
return result;
|
||||
});
|
||||
tfrpc.register(async function store_blob(blob) {
|
||||
if (typeof(blob) == 'string') {
|
||||
blob = utf8Encode(blob);
|
||||
}
|
||||
if (Array.isArray(blob)) {
|
||||
blob = Uint8Array.from(blob);
|
||||
}
|
||||
return await ssb.blobStore(blob);
|
||||
});
|
||||
|
||||
tfrpc.register(async function get_blob(id) {
|
||||
return utf8Decode(await ssb.blobGet(id));
|
||||
});
|
||||
tfrpc.register(strava.refresh_token);
|
||||
|
||||
async function main() {
|
||||
g_shared_database = await shared_database('state');
|
||||
if (core.user.credentials?.session?.name) {
|
||||
g_database = await database('state');
|
||||
}
|
||||
|
||||
let attempt;
|
||||
if (core.user.credentials?.session?.name) {
|
||||
let shared_db = await shared_database('state');
|
||||
attempt = await shared_db.get(core.user.credentials.session.name);
|
||||
}
|
||||
app.setDocument(utf8Decode(getFile('index.html')).replace('${data}', JSON.stringify({
|
||||
attempt: attempt,
|
||||
state: core.user?.credentials?.session?.name,
|
||||
})));
|
||||
}
|
||||
|
||||
main();
|
81
apps/gg/gpx.js
Normal file
81
apps/gg/gpx.js
Normal file
@ -0,0 +1,81 @@
|
||||
function xml_parse(xml) {
|
||||
let result;
|
||||
let path = [];
|
||||
let tag_begin;
|
||||
let text_begin;
|
||||
for (let i = 0; i < xml.length; i++) {
|
||||
let c = xml.charAt(i);
|
||||
if (!tag_begin && c == '<') {
|
||||
if (i > text_begin && path.length) {
|
||||
let value = xml.substring(text_begin, i);
|
||||
if (!/^\s*$/.test(value)) {
|
||||
path[path.length - 1].value = value;
|
||||
}
|
||||
}
|
||||
tag_begin = i + 1;
|
||||
} else if (tag_begin && c == '>') {
|
||||
let tag = xml.substring(tag_begin, i).trim();
|
||||
if (tag.startsWith('?') && tag.endsWith('?')) {
|
||||
/* Ignore directives. */
|
||||
} else if (tag.startsWith('/')) {
|
||||
path.pop();
|
||||
} else {
|
||||
let parts = tag.split(' ');
|
||||
let attributes = {};
|
||||
for (let j = 1; j < parts.length; j++) {
|
||||
let eq = parts[j].indexOf('=');
|
||||
let value = parts[j].substring(eq + 1);
|
||||
if (value.startsWith('"') && value.endsWith('"')) {
|
||||
value = value.substring(1, value.length - 1);
|
||||
}
|
||||
attributes[parts[j].substring(0, eq)] = value;
|
||||
}
|
||||
let next = {name: parts[0], children: [], attributes: attributes};
|
||||
if (path.length) {
|
||||
path[path.length - 1].children.push(next);
|
||||
} else {
|
||||
result = next;
|
||||
}
|
||||
if (!tag.endsWith('/')) {
|
||||
path.push(next);
|
||||
}
|
||||
}
|
||||
tag_begin = undefined;
|
||||
text_begin = i + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function* xml_each(node, name) {
|
||||
for (let child of node.children) {
|
||||
if (child.name == name) {
|
||||
yield child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function gpx_parse(xml) {
|
||||
let result = {segments: []};
|
||||
let tree = xml_parse(xml);
|
||||
if (tree?.name == 'gpx') {
|
||||
for (let trk of xml_each(tree, 'trk')) {
|
||||
for (let trkseg of xml_each(trk, 'trkseg')) {
|
||||
let segment = [];
|
||||
for (let trkpt of xml_each(trkseg, 'trkpt')) {
|
||||
segment.push({lat: parseFloat(trkpt.attributes.lat), lon: parseFloat(trkpt.attributes.lon)});
|
||||
}
|
||||
result.segments.push(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let metadata of xml_each(tree, 'metadata')) {
|
||||
for (let link of xml_each(metadata, 'link')) {
|
||||
result.link = link.attributes.href;
|
||||
}
|
||||
for (let time of xml_each(metadata, 'time')) {
|
||||
result.time = time.value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
21
apps/gg/handler.js
Normal file
21
apps/gg/handler.js
Normal file
@ -0,0 +1,21 @@
|
||||
import * as strava from './strava.js';
|
||||
|
||||
async function main() {
|
||||
print('handler running');
|
||||
let r = await strava.authorization_code(request.query.code);
|
||||
print('state =', request.query.state);
|
||||
print('body = ', r.body);
|
||||
if (request.query.state && r.body) {
|
||||
let shared_db = await shared_database('state');
|
||||
await shared_db.set(request.query.state, utf8Decode(r.body));
|
||||
}
|
||||
await respond({
|
||||
data: r.body,
|
||||
content_type: 'text/plain',
|
||||
headers: {
|
||||
Location: 'https://tildefriends.net/~cory/gg/',
|
||||
},
|
||||
status_code: 307,
|
||||
});
|
||||
}
|
||||
main();
|
14
apps/gg/index.html
Normal file
14
apps/gg/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="width: 100%; height: 100%; margin: 0; padding: 0">
|
||||
<head>
|
||||
<script>window.litDisableBundleWarning = true;</script>
|
||||
<script>
|
||||
let g_data = ${data};
|
||||
</script>
|
||||
<script src="script.js" type="module"></script>
|
||||
<script src="leaflet.js"></script>
|
||||
</head>
|
||||
<body style="color: #fff; display: flex; flex-flow: column; height: 100%; width: 100%; margin: 0; padding: 0">
|
||||
<gg-app style="width: 100%; height: 100%" id="ggapp"></gg-app>
|
||||
</body>
|
||||
</html>
|
661
apps/gg/leaflet.css
Normal file
661
apps/gg/leaflet.css
Normal file
@ -0,0 +1,661 @@
|
||||
/* required styles */
|
||||
|
||||
.leaflet-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-container,
|
||||
.leaflet-pane > svg,
|
||||
.leaflet-pane > canvas,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
/* Prevents IE11 from highlighting tiles in blue */
|
||||
.leaflet-tile::selection {
|
||||
background: transparent;
|
||||
}
|
||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
||||
.leaflet-safari .leaflet-tile {
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
/* hack that prevents hw layers "stretching" when loading new tiles */
|
||||
.leaflet-safari .leaflet-tile-container {
|
||||
width: 1600px;
|
||||
height: 1600px;
|
||||
-webkit-transform-origin: 0 0;
|
||||
}
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||
.leaflet-container .leaflet-overlay-pane svg {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
.leaflet-container .leaflet-marker-pane img,
|
||||
.leaflet-container .leaflet-shadow-pane img,
|
||||
.leaflet-container .leaflet-tile-pane img,
|
||||
.leaflet-container img.leaflet-image-layer,
|
||||
.leaflet-container .leaflet-tile {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.leaflet-container img.leaflet-tile {
|
||||
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
|
||||
.leaflet-container.leaflet-touch-zoom {
|
||||
-ms-touch-action: pan-x pan-y;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag {
|
||||
-ms-touch-action: pinch-zoom;
|
||||
/* Fallback for FF which doesn't support pinch-zoom */
|
||||
touch-action: none;
|
||||
touch-action: pinch-zoom;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.leaflet-container {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.leaflet-container a {
|
||||
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
|
||||
}
|
||||
.leaflet-tile {
|
||||
filter: inherit;
|
||||
visibility: hidden;
|
||||
}
|
||||
.leaflet-tile-loaded {
|
||||
visibility: inherit;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
width: 0;
|
||||
height: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 800;
|
||||
}
|
||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||
.leaflet-overlay-pane svg {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.leaflet-pane { z-index: 400; }
|
||||
|
||||
.leaflet-tile-pane { z-index: 200; }
|
||||
.leaflet-overlay-pane { z-index: 400; }
|
||||
.leaflet-shadow-pane { z-index: 500; }
|
||||
.leaflet-marker-pane { z-index: 600; }
|
||||
.leaflet-tooltip-pane { z-index: 650; }
|
||||
.leaflet-popup-pane { z-index: 700; }
|
||||
|
||||
.leaflet-map-pane canvas { z-index: 100; }
|
||||
.leaflet-map-pane svg { z-index: 200; }
|
||||
|
||||
.leaflet-vml-shape {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.lvml {
|
||||
behavior: url(#default#VML);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
/* control positioning */
|
||||
|
||||
.leaflet-control {
|
||||
position: relative;
|
||||
z-index: 800;
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-top {
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-right {
|
||||
right: 0;
|
||||
}
|
||||
.leaflet-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.leaflet-left {
|
||||
left: 0;
|
||||
}
|
||||
.leaflet-control {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
float: right;
|
||||
}
|
||||
.leaflet-top .leaflet-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.leaflet-left .leaflet-control {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom and fade animations */
|
||||
|
||||
.leaflet-fade-anim .leaflet-popup {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||
opacity: 1;
|
||||
}
|
||||
.leaflet-zoom-animated {
|
||||
-webkit-transform-origin: 0 0;
|
||||
-ms-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
svg.leaflet-zoom-animated {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-tile,
|
||||
.leaflet-pan-anim .leaflet-tile {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* cursors */
|
||||
|
||||
.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
.leaflet-grab {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: grab;
|
||||
}
|
||||
.leaflet-crosshair,
|
||||
.leaflet-crosshair .leaflet-interactive {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-control {
|
||||
cursor: auto;
|
||||
}
|
||||
.leaflet-dragging .leaflet-grab,
|
||||
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
||||
.leaflet-dragging .leaflet-marker-draggable {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* marker & overlays interactivity */
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-pane > svg path,
|
||||
.leaflet-tile-container {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon.leaflet-interactive,
|
||||
.leaflet-image-layer.leaflet-interactive,
|
||||
.leaflet-pane > svg path.leaflet-interactive,
|
||||
svg.leaflet-image-layer.leaflet-interactive path {
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* visual tweaks */
|
||||
|
||||
.leaflet-container {
|
||||
background: #ddd;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.leaflet-container a {
|
||||
color: #0078A8;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
border: 2px dotted #38f;
|
||||
background: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
|
||||
/* general typography */
|
||||
.leaflet-container {
|
||||
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
/* general toolbar styles */
|
||||
|
||||
.leaflet-bar {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-control-layers-toggle {
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
.leaflet-bar a:hover,
|
||||
.leaflet-bar a:focus {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.leaflet-bar a:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.leaflet-bar a.leaflet-disabled {
|
||||
cursor: default;
|
||||
background-color: #f4f4f4;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-bar a {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:first-child {
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
/* zoom control */
|
||||
|
||||
.leaflet-control-zoom-in,
|
||||
.leaflet-control-zoom-out {
|
||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||
text-indent: 1px;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
||||
/* layers control */
|
||||
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.leaflet-retina .leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers-2x.png);
|
||||
background-size: 26px 26px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers-toggle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.leaflet-control-layers .leaflet-control-layers-list,
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||
display: none;
|
||||
}
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 6px 10px 6px 6px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.leaflet-control-layers-scrollbar {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.leaflet-control-layers-selector {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.leaflet-control-layers label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-size: 1.08333em;
|
||||
}
|
||||
.leaflet-control-layers-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 5px -10px 5px -6px;
|
||||
}
|
||||
|
||||
/* Default icon URLs */
|
||||
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
|
||||
background-image: url(images/marker-icon.png);
|
||||
}
|
||||
|
||||
|
||||
/* attribution and scale controls */
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-scale-line {
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.leaflet-control-attribution a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.leaflet-control-attribution a:hover,
|
||||
.leaflet-control-attribution a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.leaflet-attribution-flag {
|
||||
display: inline !important;
|
||||
vertical-align: baseline !important;
|
||||
width: 1em;
|
||||
height: 0.6669em;
|
||||
}
|
||||
.leaflet-left .leaflet-control-scale {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-scale {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.leaflet-control-scale-line {
|
||||
border: 2px solid #777;
|
||||
border-top: none;
|
||||
line-height: 1.1;
|
||||
padding: 2px 5px 1px;
|
||||
white-space: nowrap;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
text-shadow: 1px 1px #fff;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child) {
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||
border-bottom: 2px solid #777;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-attribution,
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
border: 2px solid rgba(0,0,0,0.2);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
|
||||
/* popup */
|
||||
|
||||
.leaflet-popup {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 13px 24px 13px 20px;
|
||||
line-height: 1.3;
|
||||
font-size: 13px;
|
||||
font-size: 1.08333em;
|
||||
min-height: 1px;
|
||||
}
|
||||
.leaflet-popup-content p {
|
||||
margin: 17px 0;
|
||||
margin: 1.3em 0;
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-top: -1px;
|
||||
margin-left: -20px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
|
||||
margin: -10px auto 0;
|
||||
pointer-events: auto;
|
||||
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.leaflet-popup-content-wrapper,
|
||||
.leaflet-popup-tip {
|
||||
background: white;
|
||||
color: #333;
|
||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border: none;
|
||||
text-align: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font: 16px/24px Tahoma, Verdana, sans-serif;
|
||||
color: #757575;
|
||||
text-decoration: none;
|
||||
background: transparent;
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button:hover,
|
||||
.leaflet-container a.leaflet-popup-close-button:focus {
|
||||
color: #585858;
|
||||
}
|
||||
.leaflet-popup-scrolled {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||
-ms-zoom: 1;
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
width: 24px;
|
||||
margin: 0 auto;
|
||||
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-control-zoom,
|
||||
.leaflet-oldie .leaflet-control-layers,
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
|
||||
/* div icon */
|
||||
|
||||
.leaflet-div-icon {
|
||||
background: #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
|
||||
|
||||
/* Tooltip */
|
||||
/* Base styles for the element that has a tooltip */
|
||||
.leaflet-tooltip {
|
||||
position: absolute;
|
||||
padding: 6px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 3px;
|
||||
color: #222;
|
||||
white-space: nowrap;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-tooltip.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-tooltip-top:before,
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border: 6px solid transparent;
|
||||
background: transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Directions */
|
||||
|
||||
.leaflet-tooltip-bottom {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.leaflet-tooltip-top {
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-top:before {
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-top:before {
|
||||
bottom: 0;
|
||||
margin-bottom: -12px;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before {
|
||||
top: 0;
|
||||
margin-top: -12px;
|
||||
margin-left: -6px;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-left {
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-right {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
top: 50%;
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before {
|
||||
right: 0;
|
||||
margin-right: -12px;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-right:before {
|
||||
left: 0;
|
||||
margin-left: -12px;
|
||||
border-right-color: #fff;
|
||||
}
|
||||
|
||||
/* Printing */
|
||||
|
||||
@media print {
|
||||
/* Prevent printers from removing background-images of controls. */
|
||||
.leaflet-control {
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
}
|
6
apps/gg/leaflet.js
Normal file
6
apps/gg/leaflet.js
Normal file
File diff suppressed because one or more lines are too long
1
apps/gg/leaflet.js.map
Normal file
1
apps/gg/leaflet.js.map
Normal file
File diff suppressed because one or more lines are too long
126
apps/gg/lit-all.min.js
vendored
Normal file
126
apps/gg/lit-all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
apps/gg/lit-all.min.js.map
Normal file
1
apps/gg/lit-all.min.js.map
Normal file
File diff suppressed because one or more lines are too long
158
apps/gg/polyline.js
Normal file
158
apps/gg/polyline.js
Normal file
@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Based off of [the offical Google document](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)
|
||||
*
|
||||
* Some parts from [this implementation](http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.js)
|
||||
* by [Mark McClure](http://facstaff.unca.edu/mcmcclur/)
|
||||
*
|
||||
* @module polyline
|
||||
*/
|
||||
|
||||
var polyline = {};
|
||||
|
||||
function py2_round(value) {
|
||||
// Google's polyline algorithm uses the same rounding strategy as Python 2, which is different from JS for negative values
|
||||
return Math.floor(Math.abs(value) + 0.5) * (value >= 0 ? 1 : -1);
|
||||
}
|
||||
|
||||
function encode(current, previous, factor) {
|
||||
current = py2_round(current * factor);
|
||||
previous = py2_round(previous * factor);
|
||||
var coordinate = (current - previous) * 2;
|
||||
if (coordinate < 0) {
|
||||
coordinate = -coordinate - 1
|
||||
}
|
||||
var output = '';
|
||||
while (coordinate >= 0x20) {
|
||||
output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63);
|
||||
coordinate /= 32;
|
||||
}
|
||||
output += String.fromCharCode((coordinate | 0) + 63);
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes to a [latitude, longitude] coordinates array.
|
||||
*
|
||||
* This is adapted from the implementation in Project-OSRM.
|
||||
*
|
||||
* @param {String} str
|
||||
* @param {Number} precision
|
||||
* @returns {Array}
|
||||
*
|
||||
* @see https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js
|
||||
*/
|
||||
polyline.decode = function(str, precision) {
|
||||
var index = 0,
|
||||
lat = 0,
|
||||
lng = 0,
|
||||
coordinates = [],
|
||||
shift = 0,
|
||||
result = 0,
|
||||
byte = null,
|
||||
latitude_change,
|
||||
longitude_change,
|
||||
factor = Math.pow(10, Number.isInteger(precision) ? precision : 5);
|
||||
|
||||
// Coordinates have variable length when encoded, so just keep
|
||||
// track of whether we've hit the end of the string. In each
|
||||
// loop iteration, a single coordinate is decoded.
|
||||
while (index < str.length) {
|
||||
|
||||
// Reset shift, result, and byte
|
||||
byte = null;
|
||||
shift = 1;
|
||||
result = 0;
|
||||
|
||||
do {
|
||||
byte = str.charCodeAt(index++) - 63;
|
||||
result += (byte & 0x1f) * shift;
|
||||
shift *= 32;
|
||||
} while (byte >= 0x20);
|
||||
|
||||
latitude_change = (result & 1) ? ((-result - 1) / 2) : (result / 2);
|
||||
|
||||
shift = 1;
|
||||
result = 0;
|
||||
|
||||
do {
|
||||
byte = str.charCodeAt(index++) - 63;
|
||||
result += (byte & 0x1f) * shift;
|
||||
shift *= 32;
|
||||
} while (byte >= 0x20);
|
||||
|
||||
longitude_change = (result & 1) ? ((-result - 1) / 2) : (result / 2);
|
||||
|
||||
lat += latitude_change;
|
||||
lng += longitude_change;
|
||||
|
||||
coordinates.push([lat / factor, lng / factor]);
|
||||
}
|
||||
|
||||
return coordinates;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encodes the given [latitude, longitude] coordinates array.
|
||||
*
|
||||
* @param {Array.<Array.<Number>>} coordinates
|
||||
* @param {Number} precision
|
||||
* @returns {String}
|
||||
*/
|
||||
polyline.encode = function(coordinates, precision) {
|
||||
if (!coordinates.length) { return ''; }
|
||||
|
||||
var factor = Math.pow(10, Number.isInteger(precision) ? precision : 5),
|
||||
output = encode(coordinates[0][0], 0, factor) + encode(coordinates[0][1], 0, factor);
|
||||
|
||||
for (var i = 1; i < coordinates.length; i++) {
|
||||
var a = coordinates[i], b = coordinates[i - 1];
|
||||
output += encode(a[0], b[0], factor);
|
||||
output += encode(a[1], b[1], factor);
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
function flipped(coords) {
|
||||
var flipped = [];
|
||||
for (var i = 0; i < coords.length; i++) {
|
||||
var coord = coords[i].slice();
|
||||
flipped.push([coord[1], coord[0]]);
|
||||
}
|
||||
return flipped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a GeoJSON LineString feature/geometry.
|
||||
*
|
||||
* @param {Object} geojson
|
||||
* @param {Number} precision
|
||||
* @returns {String}
|
||||
*/
|
||||
polyline.fromGeoJSON = function(geojson, precision) {
|
||||
if (geojson && geojson.type === 'Feature') {
|
||||
geojson = geojson.geometry;
|
||||
}
|
||||
if (!geojson || geojson.type !== 'LineString') {
|
||||
throw new Error('Input must be a GeoJSON LineString');
|
||||
}
|
||||
return polyline.encode(flipped(geojson.coordinates), precision);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes to a GeoJSON LineString geometry.
|
||||
*
|
||||
* @param {String} str
|
||||
* @param {Number} precision
|
||||
* @returns {Object}
|
||||
*/
|
||||
polyline.toGeoJSON = function(str, precision) {
|
||||
var coords = polyline.decode(str, precision);
|
||||
return {
|
||||
type: 'LineString',
|
||||
coordinates: flipped(coords)
|
||||
};
|
||||
};
|
||||
|
||||
let polyline_decode = polyline.decode;
|
||||
export { polyline_decode as decode };
|
789
apps/gg/script.js
Normal file
789
apps/gg/script.js
Normal file
@ -0,0 +1,789 @@
|
||||
import {LitElement, html, unsafeHTML, css, guard, until} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import * as polyline from './polyline.js';
|
||||
import {gpx_parse} from './gpx.js';
|
||||
|
||||
const k_client_id = '28276';
|
||||
const k_redirect_url = 'https://tildefriends.net/~cory/gg/login';
|
||||
|
||||
const k_color_snow = [128, 128, 255, 255];
|
||||
const k_color_ice = [160, 160, 255, 255];
|
||||
const k_color_water = [0, 0, 255, 255];
|
||||
const k_color_dirt = [128, 129, 130, 255];
|
||||
const k_color_pavement = [32, 32, 32, 255];
|
||||
const k_color_grass = [0, 255, 0, 255];
|
||||
const k_color_default = [128, 128, 128, 255];
|
||||
|
||||
const k_store = {
|
||||
'🦞': 15,
|
||||
'🛶': 10,
|
||||
'🏠': 10,
|
||||
'⛰': 10,
|
||||
};
|
||||
|
||||
const k_marker_snap = {x: 5, y: 1};
|
||||
|
||||
class GgAppElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
user: {type: Object},
|
||||
strava: {type: Object},
|
||||
activities: {type: Array},
|
||||
activity: {type: Object},
|
||||
world: {type: Object},
|
||||
whoami: {type: String},
|
||||
status: {type: Object},
|
||||
tab: {type: String},
|
||||
url: {type: String},
|
||||
currency: {type: Number},
|
||||
to_build: {type: String},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.activities = [];
|
||||
this.activity = {};
|
||||
this.loaded_activities = [];
|
||||
this.placed_emojis = [];
|
||||
this.strava = {};
|
||||
this.min_lat = Number.MAX_VALUE;
|
||||
this.min_lon = Number.MAX_VALUE;
|
||||
this.max_lat = -Number.MAX_VALUE;
|
||||
this.max_lon = -Number.MAX_VALUE;
|
||||
this.focus = undefined;
|
||||
this.status = undefined;
|
||||
this.tab = 'map';
|
||||
this.load().catch(function(e) {
|
||||
console.log('load error', e);
|
||||
});
|
||||
this.to_build = '🏠';
|
||||
}
|
||||
|
||||
async load() {
|
||||
console.log('load');
|
||||
this.user = await tfrpc.rpc.getUser();
|
||||
this.url = (await tfrpc.rpc.url()).split('?')[0];
|
||||
try {
|
||||
await this.update_credentials();
|
||||
} catch (e) {
|
||||
console.log('update_credentials failed', e);
|
||||
}
|
||||
try {
|
||||
await this.update_activities();
|
||||
} catch (e) {
|
||||
console.log('update_activities failed', e);
|
||||
}
|
||||
await this.acquire_ssb_identity();
|
||||
if (this.whoami && this.activities?.length) {
|
||||
await this.sync_activities();
|
||||
}
|
||||
await this.get_activities_from_ssb();
|
||||
}
|
||||
|
||||
/* https://gist.github.com/jcouyang/632709f30e12a7879a73e9e132c0d56b?permalink_comment_id=3591045#gistcomment-3591045 */
|
||||
async promise_all(promises, max_concurrent) {
|
||||
let index = 0;
|
||||
let results = [];
|
||||
async function exec_thread() {
|
||||
while (index < promises.length) {
|
||||
const current = index++;
|
||||
results[current] = await promises[current];
|
||||
}
|
||||
}
|
||||
const threads = [];
|
||||
for (let thread = 0; thread < max_concurrent; thread++) {
|
||||
threads.push(exec_thread());
|
||||
}
|
||||
await Promise.all(threads);
|
||||
return results;
|
||||
}
|
||||
|
||||
async get_activities_from_ssb() {
|
||||
this.status = {text: 'loading activities'};
|
||||
this.loaded_activities = [];
|
||||
let rows = await tfrpc.rpc.query(`
|
||||
SELECT messages.author, json_extract(mention.value, '$.link') AS blob_id
|
||||
FROM messages_fts('"gg-activity"')
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid,
|
||||
json_each(messages.content, '$.mentions') as mention
|
||||
WHERE json_extract(messages.content, '$.type') = 'gg-activity' AND
|
||||
json_extract(mention.value, '$.name') = 'activity_data'
|
||||
ORDER BY messages.timestamp DESC
|
||||
`, []);
|
||||
this.status = {text: 'loading activity data'};
|
||||
let authors = rows.map(x => x.author);
|
||||
let blobs = await this.promise_all(rows.map(x => tfrpc.rpc.get_blob(x.blob_id)), 8);
|
||||
this.status = {text: 'processing activity data'};
|
||||
for (let [index, blob] of blobs.entries()) {
|
||||
let activity;
|
||||
try {
|
||||
activity = JSON.parse(blob);
|
||||
} catch {
|
||||
activity = gpx_parse(blob);
|
||||
}
|
||||
if (activity) {
|
||||
activity.author = authors[index];
|
||||
this.loaded_activities.push(activity);
|
||||
}
|
||||
}
|
||||
this.status = {text: 'calculating balance'};
|
||||
rows = await tfrpc.rpc.query(`
|
||||
SELECT count(*) AS currency FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-activity'
|
||||
`, [this.whoami]);
|
||||
let currency = rows[0].currency;
|
||||
rows = await tfrpc.rpc.query(`
|
||||
SELECT SUM(json_extract(content, '$.cost')) AS cost FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-place'
|
||||
`, [this.whoami]);
|
||||
let spent = rows[0].cost;
|
||||
this.currency = currency - spent;
|
||||
this.status = {text: 'getting placed emojis'};
|
||||
rows = await tfrpc.rpc.query(`
|
||||
SELECT messages.content
|
||||
FROM messages_fts('"gg-place"')
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
WHERE json_extract(messages.content, '$.type') = 'gg-place'
|
||||
ORDER BY messages.timestamp
|
||||
`);
|
||||
for (let row of rows) {
|
||||
console.log(row.content);
|
||||
let content = JSON.parse(row.content);
|
||||
this.placed_emojis.push({
|
||||
position: content.position,
|
||||
emoji: content.emoji,
|
||||
});
|
||||
}
|
||||
console.log(this.placed_emojis);
|
||||
this.status = undefined;
|
||||
this.update_map();
|
||||
}
|
||||
|
||||
async sync_activities() {
|
||||
let ids = this.activities.map(x => `https://www.strava.com/activities/${x.id}`);
|
||||
let missing = await tfrpc.rpc.query(`
|
||||
WITH my_activities AS (
|
||||
SELECT json_extract(mention.value, '$.link') AS url
|
||||
FROM messages, json_each(messages.content, '$.mentions') AS mention
|
||||
WHERE
|
||||
author = ? AND
|
||||
json_extract(messages.content, '$.type') = 'gg-activity' AND
|
||||
json_extract(mention.value, '$.name') = 'activity_url')
|
||||
SELECT from_strava.value FROM json_each(?) AS from_strava
|
||||
LEFT OUTER JOIN my_activities ON from_strava.value = my_activities.url
|
||||
WHERE my_activities.url IS NULL
|
||||
`, [this.whoami, JSON.stringify(ids)]);
|
||||
console.log('missing = ', missing);
|
||||
for (let [index, row] of missing.entries()) {
|
||||
this.status = {text: 'syncing from strava', value: index, max: missing.length};
|
||||
let url = row.value;
|
||||
let id = url.match(/.*\/(\d+)/)[1];
|
||||
let response = await fetch(`https://www.strava.com/api/v3/activities/${id}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.strava.access_token}`,
|
||||
},
|
||||
});
|
||||
let activity = await response.json();
|
||||
let blob_id = await tfrpc.rpc.store_blob(JSON.stringify(activity));
|
||||
let message = {
|
||||
type: 'gg-activity',
|
||||
mentions: [
|
||||
{
|
||||
link: url,
|
||||
name: 'activity_url',
|
||||
},
|
||||
{
|
||||
link: blob_id,
|
||||
name: 'activity_data',
|
||||
}
|
||||
],
|
||||
};
|
||||
await tfrpc.rpc.appendMessage(this.whoami, message);
|
||||
}
|
||||
this.status = undefined;
|
||||
}
|
||||
|
||||
async acquire_ssb_identity() {
|
||||
let user = await tfrpc.rpc.getUser();
|
||||
if (!user?.credentials?.session?.name) {
|
||||
return;
|
||||
}
|
||||
let ids = await tfrpc.rpc.getIdentities();
|
||||
let players = ids.length ? (await tfrpc.rpc.query(`
|
||||
SELECT author FROM messages JOIN json_each(?) ON messages.author = json_each.value
|
||||
WHERE
|
||||
json_extract(messages.content, '$.type') = 'gg-player' AND
|
||||
json_extract(messages.content, '$.active')
|
||||
ORDER BY timestamp DESC limit 1
|
||||
`, [JSON.stringify(ids)])).map(row => row.author) : [];
|
||||
if (!players.length) {
|
||||
this.whoami = await tfrpc.rpc.createIdentity();
|
||||
if (this.whoami) {
|
||||
await tfrpc.rpc.appendMessage(this.whoami, {
|
||||
type: 'gg-player',
|
||||
active: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
players.sort();
|
||||
this.whoami = players[0];
|
||||
}
|
||||
}
|
||||
|
||||
async update_credentials() {
|
||||
let name = this.user?.credentials?.session?.name;
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
let shared = await tfrpc.rpc.sharedDatabaseGet(name);
|
||||
if (shared) {
|
||||
await tfrpc.rpc.databaseSet('strava', shared);
|
||||
await tfrpc.rpc.sharedDatabaseRemove(name);
|
||||
}
|
||||
this.strava = JSON.parse(await tfrpc.rpc.databaseGet('strava') || '{}');
|
||||
if (new Date().valueOf() / 1000 > this.strava.expires_at) {
|
||||
console.log('this looks expired', new Date().valueOf() / 1000, '>', this.strava.expires_at);
|
||||
let x = await tfrpc.rpc.refresh_token(this.strava);
|
||||
if (x) {
|
||||
this.strava = x;
|
||||
await tfrpc.rpc.databaseSet('strava', JSON.stringify(x));
|
||||
} else {
|
||||
this.strava = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async update_activities() {
|
||||
if (this?.strava?.access_token) {
|
||||
let response = await fetch('https://www.strava.com/api/v3/athlete/activities', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.strava.access_token}`,
|
||||
},
|
||||
});
|
||||
this.activities = await response.json();
|
||||
this.activities.sort((a, b) => (a.id - b.id));
|
||||
}
|
||||
}
|
||||
|
||||
color_to_emoji(color) {
|
||||
const k_map = [
|
||||
[k_color_snow, '⬜'],
|
||||
[k_color_ice, '🟦'],
|
||||
[k_color_water, '🟦'],
|
||||
[k_color_dirt, '🟫'],
|
||||
[k_color_pavement, '⬛'],
|
||||
[k_color_grass, '🟩'],
|
||||
[k_color_default, '🟧'],
|
||||
];
|
||||
for (let m of k_map) {
|
||||
if (m[0][0] == color[0] &&
|
||||
m[0][1] == color[1] &&
|
||||
m[0][2] == color[2] &&
|
||||
m[0][3] == color[3]) {
|
||||
return m[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activity_bounds(activity) {
|
||||
let min_lat = Number.MAX_VALUE;
|
||||
let min_lon = Number.MAX_VALUE;
|
||||
let max_lat = -Number.MAX_VALUE;
|
||||
let max_lon = -Number.MAX_VALUE;
|
||||
if (activity?.map?.polyline) {
|
||||
for (let pt of polyline.decode(activity.map.polyline)) {
|
||||
min_lat = Math.min(min_lat, pt[0]);
|
||||
min_lon = Math.min(min_lon, pt[1]);
|
||||
max_lat = Math.max(max_lat, pt[0]);
|
||||
max_lon = Math.max(max_lon, pt[1]);
|
||||
}
|
||||
}
|
||||
if (activity?.segments) {
|
||||
for (let segment of activity.segments) {
|
||||
for (let pt of segment) {
|
||||
min_lat = Math.min(min_lat, pt.lat);
|
||||
min_lon = Math.min(min_lon, pt.lon);
|
||||
max_lat = Math.max(max_lat, pt.lat);
|
||||
max_lon = Math.max(max_lon, pt.lon);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
min: {
|
||||
lat: min_lat,
|
||||
lng: min_lon,
|
||||
},
|
||||
max: {
|
||||
lat: max_lat,
|
||||
lng: max_lon,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
on_click(event) {
|
||||
let popup = L.popup()
|
||||
.setLatLng(event.latlng)
|
||||
.setContent(`
|
||||
<div><a target="_top" href="https://www.google.com/maps/search/?api=1&query=${event.latlng.lat},${event.latlng.lng}">${event.latlng.lat}, ${event.latlng.lng}</a></div>
|
||||
`)
|
||||
.openOn(this.leaflet);
|
||||
}
|
||||
|
||||
async build() {
|
||||
if (this.popup) {
|
||||
this.popup.remove();
|
||||
}
|
||||
if (!this.marker) {
|
||||
return;
|
||||
}
|
||||
let latlng = this.marker.getLatLng();
|
||||
|
||||
let cost = k_store[this.to_build];
|
||||
if (cost > this.currency) {
|
||||
alert('Insufficient funds.');
|
||||
return;
|
||||
}
|
||||
let message = {
|
||||
type: 'gg-place',
|
||||
position: {lat: latlng.lat, lng: latlng.lng},
|
||||
emoji: this.to_build,
|
||||
cost: cost,
|
||||
};
|
||||
let id = await tfrpc.rpc.appendMessage(this.whoami, message);
|
||||
this.marker.remove();
|
||||
this.placed_emojis.push({
|
||||
position: {lat: latlng.lat, lng: latlng.lng},
|
||||
emoji: this.to_build,
|
||||
});
|
||||
this.currency -= cost;
|
||||
return this.update_map();
|
||||
}
|
||||
|
||||
on_marker_click(event) {
|
||||
this.popup = L.popup()
|
||||
.setLatLng(event.latlng)
|
||||
.setContent(`
|
||||
${this.to_build} (-${k_store[this.to_build]}) <input type="button" value="Build" onclick="document.getElementById('ggapp').build()"></input>
|
||||
`)
|
||||
.openOn(this.leaflet);
|
||||
}
|
||||
|
||||
snap_to_grid(latlng, fudge) {
|
||||
let position = this.leaflet.options.crs.latLngToPoint(latlng, this.leaflet.getZoom());
|
||||
position.x = Math.round(position.x / 16) * 16 + (fudge?.x ?? 0);
|
||||
position.y = Math.round(position.y / 16) * 16 + (fudge?.y ?? 0);
|
||||
position = this.leaflet.options.crs.pointToLatLng(position, this.leaflet.getZoom());
|
||||
return position;
|
||||
}
|
||||
|
||||
on_marker_move(event) {
|
||||
if (!this.no_snap && this.marker) {
|
||||
this.no_snap = true;
|
||||
this.marker.setLatLng(this.snap_to_grid(this.marker.getLatLng(), k_marker_snap));
|
||||
this.no_snap = false;
|
||||
}
|
||||
}
|
||||
|
||||
on_mouse_down(event) {
|
||||
if (this.marker) {
|
||||
this.marker.remove();
|
||||
this.marker = undefined;
|
||||
}
|
||||
|
||||
if (this.to_build) {
|
||||
this.marker = L.marker(this.snap_to_grid(event.latlng, k_marker_snap), {icon: L.divIcon({className: 'build-icon'}), draggable: true}).addTo(this.leaflet);
|
||||
this.marker.on({click: this.on_marker_click.bind(this)});
|
||||
this.marker.on({drag: this.on_marker_move.bind(this)});
|
||||
}
|
||||
}
|
||||
|
||||
async update_map() {
|
||||
let map = this.shadowRoot.getElementById('map');
|
||||
if (!map || !this.loaded_activities.length) {
|
||||
this.leaflet = undefined;
|
||||
this.grid_layer = undefined;
|
||||
return;
|
||||
}
|
||||
if (!this.leaflet) {
|
||||
this.leaflet = L.map(map, {attributionControl: false, maxZoom: 16, bounceAtZoomLimits: false});
|
||||
this.leaflet.on({contextmenu: this.on_click.bind(this)});
|
||||
this.leaflet.on({click: this.on_mouse_down.bind(this)});
|
||||
}
|
||||
let self = this;
|
||||
let grid_layer = L.GridLayer.extend({
|
||||
createTile: function(coords) {
|
||||
var tile = L.DomUtil.create('canvas', 'leaflet-tile');
|
||||
var size = this.getTileSize();
|
||||
tile.width = size.x;
|
||||
tile.height = size.y;
|
||||
var context = tile.getContext('2d');
|
||||
context.font = '10pt sans';
|
||||
let bounds = this._tileCoordsToBounds(coords);
|
||||
let degrees = 360.0 / (2 ** coords.z);
|
||||
let ul = bounds.getNorthWest();
|
||||
let lr = bounds.getSouthEast();
|
||||
|
||||
let mini = document.createElement('canvas');
|
||||
mini.width = Math.floor(size.x / 16.0);
|
||||
mini.height = Math.floor(size.y / 16.0);
|
||||
let mini_context = mini.getContext('2d');
|
||||
let image_data = context.getImageData(0, 0, mini.width, mini.height);
|
||||
for (let activity of self.loaded_activities) {
|
||||
self.draw_activity_to_tile(image_data, mini.width, mini.height, ul, lr, activity);
|
||||
}
|
||||
context.textAlign = 'left';
|
||||
context.textBaseline = 'top';
|
||||
for (let x = 0; x < mini.width; x++) {
|
||||
for (let y = 0; y < mini.height; y++) {
|
||||
let start = (y * mini.width + x) * 4;
|
||||
let pixel = self.color_to_emoji(image_data.data.slice(start, start + 4));
|
||||
if (pixel) {
|
||||
context.fillText(pixel, x * size.x / mini.width, y * size.y / mini.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let placed of self.placed_emojis) {
|
||||
let position = self.leaflet.options.crs.latLngToPoint(self.snap_to_grid(placed.position), coords.z);
|
||||
let tile_x = Math.floor(position.x / size.x);
|
||||
let tile_y = Math.floor(position.y / size.y);
|
||||
position.x = position.x - tile_x * size.x;
|
||||
position.y = position.y - tile_y * size.y;
|
||||
if (tile_x == coords.x && tile_y == coords.y) {
|
||||
context.fillText(placed.emoji, position.x, position.y);
|
||||
}
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
});
|
||||
if (this.grid_layer) {
|
||||
this.grid_layer.redraw();
|
||||
} else {
|
||||
this.grid_layer = new grid_layer();
|
||||
this.grid_layer.addTo(this.leaflet);
|
||||
}
|
||||
for (let activity of this.loaded_activities) {
|
||||
let bounds = this.activity_bounds(activity);
|
||||
this.min_lat = Math.min(this.min_lat, bounds.min.lat);
|
||||
this.min_lon = Math.min(this.min_lon, bounds.min.lng);
|
||||
this.max_lat = Math.max(this.max_lat, bounds.max.lat);
|
||||
this.max_lon = Math.max(this.max_lon, bounds.max.lng);
|
||||
}
|
||||
if (this.focus) {
|
||||
this.leaflet.fitBounds([
|
||||
this.focus.min,
|
||||
this.focus.max,
|
||||
]);
|
||||
this.focus = undefined;
|
||||
} else {
|
||||
this.leaflet.fitBounds([
|
||||
[this.min_lat, this.min_lon],
|
||||
[this.max_lat, this.max_lon],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
activity_to_color(activity) {
|
||||
let color = [0, 0, 0, 255];
|
||||
switch (activity.sport_type) {
|
||||
/* Implies snow. */
|
||||
case 'AlpineSki':
|
||||
case 'BackcountrySki':
|
||||
case 'NordicSki':
|
||||
case 'Snowshoe':
|
||||
case 'Snowboard':
|
||||
color = k_color_snow;
|
||||
break;
|
||||
|
||||
/* Implies ice. */
|
||||
case 'IceSkate':
|
||||
case 'InlineSkate':
|
||||
color = k_color_ice;
|
||||
break;
|
||||
|
||||
/* Implies water. */
|
||||
case 'Canoeing':
|
||||
case 'Kayaking':
|
||||
case 'Kitesurf':
|
||||
case 'Rowing':
|
||||
case 'Sail':
|
||||
case 'StandUpPaddling':
|
||||
case 'Surfing':
|
||||
case 'Swim':
|
||||
case 'Windsurf':
|
||||
color = k_color_water;
|
||||
break;
|
||||
|
||||
/* Implies dirt. */
|
||||
case 'EMountainBikeRide':
|
||||
case 'Hike':
|
||||
case 'MountainBikeRide':
|
||||
case 'RockClimbing':
|
||||
case 'TrailRun':
|
||||
color = k_color_dirt;
|
||||
break;
|
||||
|
||||
/* Implies pavement. */
|
||||
case 'EBikeRide':
|
||||
case 'GravelRide':
|
||||
case 'Handcycle':
|
||||
case 'Ride':
|
||||
case 'RollerSki':
|
||||
case 'Run':
|
||||
case 'Skateboard':
|
||||
case 'Badminton':
|
||||
case 'Tennis':
|
||||
case 'Velomobile':
|
||||
case 'Walk':
|
||||
case 'Wheelchair':
|
||||
color = k_color_pavement;
|
||||
break;
|
||||
|
||||
/* Grass, maybe? */
|
||||
case 'Golf':
|
||||
case 'Soccer':
|
||||
case 'Squash':
|
||||
color = k_color_grass;
|
||||
break;
|
||||
|
||||
// Crossfit,
|
||||
// Elliptical
|
||||
// HighIntensityIntervalTraining
|
||||
// Pickleball
|
||||
// Pilates
|
||||
// Racquetball
|
||||
// StairStepper
|
||||
// TableTennis,
|
||||
// VirtualRide
|
||||
// VirtualRow
|
||||
// VirtualRun
|
||||
// WeightTraining
|
||||
// Workout
|
||||
// Yoga
|
||||
default:
|
||||
color = k_color_default;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
line(image_data, x0, y0, x1, y1, value) {
|
||||
/* <3 https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm */
|
||||
let dx = Math.abs(x1 - x0);
|
||||
let sx = x0 < x1 ? 1 : -1;
|
||||
let dy = -Math.abs(y1 - y0);
|
||||
let sy = y0 < y1 ? 1 : -1;
|
||||
let error = dx + dy;
|
||||
while (true) {
|
||||
if (x0 >= 0 && y0 >= 0 && x0 < image_data.width && y0 < image_data.height) {
|
||||
let base = (y0 * image_data.width + x0) * 4;
|
||||
image_data.data[base + 0] = value[0];
|
||||
image_data.data[base + 1] = value[1];
|
||||
image_data.data[base + 2] = value[2];
|
||||
image_data.data[base + 3] = value[3];
|
||||
}
|
||||
|
||||
if (x0 == x1 && y0 == y1) {
|
||||
break;
|
||||
}
|
||||
let e2 = 2 * error;
|
||||
if (e2 >= dy) {
|
||||
if (x0 == x1) {
|
||||
break;
|
||||
}
|
||||
error += dy;
|
||||
x0 = Math.round(x0 + sx);
|
||||
}
|
||||
if (e2 <= dx) {
|
||||
if (y0 == y1) {
|
||||
break;
|
||||
}
|
||||
error += dx;
|
||||
y0 = Math.round(y0 + sy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw_activity_to_tile(image_data, width, height, ul, lr, activity) {
|
||||
let color = this.activity_to_color(activity);
|
||||
if (activity?.map?.polyline) {
|
||||
let last;
|
||||
for (let pt of polyline.decode(activity.map.polyline)) {
|
||||
let px = [
|
||||
Math.floor(width * (pt[1] - ul.lng) / (lr.lng - ul.lng)),
|
||||
Math.floor(height * (pt[0] - ul.lat) / (lr.lat - ul.lat)),
|
||||
];
|
||||
if (last) {
|
||||
this.line(image_data, last[0], last[1], px[0], px[1], color);
|
||||
}
|
||||
last = px;
|
||||
}
|
||||
}
|
||||
if (activity?.segments) {
|
||||
for (let segment of activity.segments) {
|
||||
let last;
|
||||
for (let pt of segment) {
|
||||
let px = [
|
||||
Math.floor(width * (pt.lon - ul.lng) / (lr.lng - ul.lng)),
|
||||
Math.floor(height * (pt.lat - ul.lat) / (lr.lat - ul.lat)),
|
||||
];
|
||||
if (last) {
|
||||
this.line(image_data, last[0], last[1], px[0], px[1], color);
|
||||
}
|
||||
last = px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async on_upload(event) {
|
||||
try {
|
||||
let file = event.srcElement.files[0];
|
||||
let xml = await file.text();
|
||||
let gpx = gpx_parse(xml);
|
||||
let blob_id = await tfrpc.rpc.store_blob(xml);
|
||||
console.log('blob_id = ', blob_id);
|
||||
console.log(gpx);
|
||||
let message = {
|
||||
type: 'gg-activity',
|
||||
mentions: [
|
||||
{
|
||||
link: `https://${gpx.link}/activity/${gpx.time}`,
|
||||
name: 'activity_url',
|
||||
},
|
||||
{
|
||||
link: blob_id,
|
||||
name: 'activity_data',
|
||||
}
|
||||
],
|
||||
};
|
||||
console.log('id =', this.whoami, 'message = ', message);
|
||||
let id = await tfrpc.rpc.appendMessage(this.whoami, message);
|
||||
console.log('appended message', id);
|
||||
alert('Activity uploaded.');
|
||||
await this.get_activities_from_ssb();
|
||||
} catch (e) {
|
||||
alert(`Error: ${JSON.stringify(e, null, 2)}`);
|
||||
}
|
||||
}
|
||||
|
||||
upload() {
|
||||
let input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.onchange = (event) => this.on_upload(event);
|
||||
input.click();
|
||||
}
|
||||
|
||||
updated() {
|
||||
this.update_map();
|
||||
}
|
||||
|
||||
focus_map(activity) {
|
||||
let bounds = this.activity_bounds(activity);
|
||||
if (bounds.min.lat < bounds.max.lat &&
|
||||
bounds.min.lng < bounds.max.lng) {
|
||||
this.tab = 'map';
|
||||
this.focus = bounds;
|
||||
}
|
||||
}
|
||||
|
||||
render_news() {
|
||||
return html`
|
||||
<ul>
|
||||
${this.loaded_activities.map(x => html`
|
||||
<li style="cursor: pointer" @click=${() => this.focus_map(x)}>${x.author} ${x.name ?? x.time}</li>
|
||||
`)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
render_store_item(item) {
|
||||
let [emoji, cost] = item;
|
||||
return html`
|
||||
<div>
|
||||
<input type="button" value="${emoji}" @click=${() => this.to_build = emoji}></input> ${cost} ${emoji == this.to_build ? '<-- Will be built next' : undefined}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render_store() {
|
||||
return html`
|
||||
<h2>Store</h2>
|
||||
<div><b>Your balance:</b> ${this.currency}</div>
|
||||
${Object.entries(k_store).map(this.render_store_item.bind(this))}
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
let header;
|
||||
if (!this.user?.credentials?.session?.name) {
|
||||
header = html`<div style="flex: 1 0">Please <a target="_top" href="/login?return=${this.url}">login</a> to Tilde Friends, first.</div>`;
|
||||
} else if (!this.strava?.access_token) {
|
||||
let strava_url = `https://www.strava.com/oauth/authorize?client_id=${k_client_id}&redirect_uri=${k_redirect_url}&response_type=code&approval_prompt=auto&scope=activity%3Aread&state=${g_data.state}`;
|
||||
header = html`
|
||||
<div style="flex: 1 0; display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
|
||||
<div style="flex: 1 1">Please <a target="_top" href=${strava_url}>login</a> to Strava.</div>
|
||||
<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.whoami}</span>
|
||||
<input type="button" value="📁" @click=${this.upload}></input>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
header = html`
|
||||
<div>
|
||||
<div style="flex: 1 0; display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
|
||||
<h1>Welcome, ${this.user.credentials.session.name}</h1>
|
||||
<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.whoami}</span>
|
||||
<input type="button" value="📁" @click=${this.upload}></input>
|
||||
</div>
|
||||
<h3 ?hidden=${!this.status?.text}>${this.status?.text} <progress ?hidden=${!this.status?.max} value=${this.status?.value} max=${this.status?.max}>${this.status?.value}</progress></h3>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
let navigation = html`
|
||||
<style>
|
||||
#navigation input[type="button"] {
|
||||
min-width: 3em;
|
||||
min-height: 3em;
|
||||
flex: 1 0;
|
||||
font-size: large;
|
||||
}
|
||||
</style>
|
||||
<div id="navigation" style="display: flex; flex-direction: row">
|
||||
<input type="button" id="button_map" @click=${() => this.tab = 'map'} value="🗺️Map"></input>
|
||||
<input type="button" id="button_news" @click=${() => this.tab = 'news'} value="🏃News"></input>
|
||||
<input type="button" id="button_friends" @click=${() => this.tab = 'friends'} value="👫Friends"></input>
|
||||
<input type="button" id="button_store" @click=${() => this.tab = 'store'} value="🏗️Store"></input>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let content;
|
||||
switch (this.tab) {
|
||||
case 'map':
|
||||
content = html`<div id="map" style="width: 100%; height: 100%"></div>`;
|
||||
break;
|
||||
case 'news':
|
||||
content = this.render_news();
|
||||
break;
|
||||
case 'friends':
|
||||
content = html`<div>Friends</div>`;
|
||||
break;
|
||||
case 'store':
|
||||
content = this.render_store();
|
||||
break;
|
||||
}
|
||||
|
||||
return html`
|
||||
<style>
|
||||
.build-icon::before {
|
||||
content: '📍';
|
||||
border: 2px solid red;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="leaflet.css"/>
|
||||
<div style="width: 100%; height: 100%; display: flex; flex-direction: column">
|
||||
${header}
|
||||
<div style="flex: 1 0; overflow: scroll">${content}</div>
|
||||
${navigation}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('gg-app', GgAppElement);
|
20
apps/gg/strava.js
Normal file
20
apps/gg/strava.js
Normal file
@ -0,0 +1,20 @@
|
||||
const k_client_id = '28276';
|
||||
const k_client_secret = '3123f1f5afe132d9731111066d1d17bdb22ef27e';
|
||||
const k_access_token = 'f753e77764c26252bd2d80e7c5cc17ace51a8864';
|
||||
const k_refresh_token = 'f58d8e1b5a3ec3bf96e681589d5014f9a294f5a4';
|
||||
const k_redirect_url = 'https://tildefriends.net/~cory/gg/login';
|
||||
|
||||
export async function refresh_token(token) {
|
||||
let r = await fetch('https://www.strava.com/api/v3/oauth/token', {
|
||||
method: 'POST',
|
||||
body: `client_id=${k_client_id}&client_secret=${k_client_secret}&refresh_token=${token.refresh_token}&grant_type=refresh_token`,
|
||||
});
|
||||
return r?.body ? JSON.parse(utf8Decode(r.body)) : undefined;
|
||||
}
|
||||
|
||||
export async function authorization_code(code) {
|
||||
return await fetch('https://www.strava.com/api/v3/oauth/token', {
|
||||
method: 'POST',
|
||||
body: `client_id=${k_client_id}&client_secret=${k_client_secret}&code=${code}&grant_type=authorization_code`,
|
||||
});
|
||||
}
|
4
apps/issues.json
Normal file
4
apps/issues.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🦟"
|
||||
}
|
105
apps/issues/app.js
Normal file
105
apps/issues/app.js
Normal file
@ -0,0 +1,105 @@
|
||||
import * as tfrpc from '/tfrpc.js';
|
||||
|
||||
let g_database;
|
||||
let g_hash;
|
||||
|
||||
tfrpc.register(async function localStorageGet(key) {
|
||||
return app.localStorageGet(key);
|
||||
});
|
||||
tfrpc.register(async function localStorageSet(key, value) {
|
||||
return app.localStorageSet(key, value);
|
||||
});
|
||||
tfrpc.register(async function databaseGet(key) {
|
||||
return g_database ? g_database.get(key) : undefined;
|
||||
});
|
||||
tfrpc.register(async function databaseSet(key, value) {
|
||||
return g_database ? g_database.set(key, value) : undefined;
|
||||
});
|
||||
tfrpc.register(async function createIdentity() {
|
||||
return ssb.createIdentity();
|
||||
});
|
||||
tfrpc.register(async function getIdentities() {
|
||||
return ssb.getIdentities();
|
||||
});
|
||||
tfrpc.register(async function getAllIdentities() {
|
||||
return ssb.getAllIdentities();
|
||||
});
|
||||
tfrpc.register(async function getBroadcasts() {
|
||||
return ssb.getBroadcasts();
|
||||
});
|
||||
tfrpc.register(async function getConnections() {
|
||||
return ssb.connections();
|
||||
});
|
||||
tfrpc.register(async function getStoredConnections() {
|
||||
return ssb.storedConnections();
|
||||
});
|
||||
tfrpc.register(async function forgetStoredConnection(connection) {
|
||||
return ssb.forgetStoredConnection(connection);
|
||||
});
|
||||
tfrpc.register(async function createTunnel(portal, target) {
|
||||
return ssb.createTunnel(portal, target);
|
||||
});
|
||||
tfrpc.register(async function connect(token) {
|
||||
await ssb.connect(token);
|
||||
});
|
||||
tfrpc.register(async function closeConnection(id) {
|
||||
await ssb.closeConnection(id);
|
||||
});
|
||||
tfrpc.register(async function query(sql, args) {
|
||||
let result = [];
|
||||
await ssb.sqlAsync(sql, args, function callback(row) {
|
||||
result.push(row);
|
||||
});
|
||||
return result;
|
||||
});
|
||||
tfrpc.register(async function appendMessage(id, message) {
|
||||
return ssb.appendMessageWithIdentity(id, message);
|
||||
});
|
||||
core.register('message', async function message_handler(message) {
|
||||
if (message.event == 'hashChange') {
|
||||
g_hash = message.hash;
|
||||
await tfrpc.rpc.hashChanged(message.hash);
|
||||
}
|
||||
});
|
||||
tfrpc.register(function getHash(id, message) {
|
||||
return g_hash;
|
||||
});
|
||||
tfrpc.register(function setHash(hash) {
|
||||
return app.setHash(hash);
|
||||
});
|
||||
ssb.addEventListener('message', async function(id) {
|
||||
await tfrpc.rpc.notifyNewMessage(id);
|
||||
});
|
||||
tfrpc.register(async function store_blob(blob) {
|
||||
if (Array.isArray(blob)) {
|
||||
blob = Uint8Array.from(blob);
|
||||
}
|
||||
return await ssb.blobStore(blob);
|
||||
});
|
||||
tfrpc.register(async function get_blob(id) {
|
||||
return utf8Decode(await ssb.blobGet(id));
|
||||
});
|
||||
tfrpc.register(async function store_message(message) {
|
||||
return await ssb.storeMessage(message);
|
||||
});
|
||||
tfrpc.register(function apps() {
|
||||
return core.apps();
|
||||
});
|
||||
tfrpc.register(async function try_decrypt(id, content) {
|
||||
return await ssb.privateMessageDecrypt(id, content);
|
||||
});
|
||||
ssb.addEventListener('broadcasts', async function() {
|
||||
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
||||
});
|
||||
|
||||
core.register('onConnectionsChanged', async function() {
|
||||
await tfrpc.rpc.set('connections', await ssb.connections());
|
||||
});
|
||||
|
||||
async function main() {
|
||||
if (typeof(database) !== 'undefined') {
|
||||
g_database = await database('ssb');
|
||||
}
|
||||
await app.setDocument(utf8Decode(await getFile('index.html')));
|
||||
}
|
||||
main();
|
91
apps/issues/commonmark-linkify.js
Normal file
91
apps/issues/commonmark-linkify.js
Normal file
@ -0,0 +1,91 @@
|
||||
function textNode(text) {
|
||||
const node = new commonmark.Node("text", undefined);
|
||||
node.literal = text;
|
||||
return node;
|
||||
}
|
||||
|
||||
function linkNode(text, url) {
|
||||
const urlNode = new commonmark.Node("link", undefined);
|
||||
urlNode.destination = url;
|
||||
urlNode.appendChild(textNode(text));
|
||||
|
||||
return urlNode;
|
||||
}
|
||||
|
||||
function splitMatches(text, regexp) {
|
||||
// Regexp must be sticky.
|
||||
regexp = new RegExp(regexp, "gm");
|
||||
|
||||
let i = 0;
|
||||
const result = [];
|
||||
|
||||
let match = regexp.exec(text);
|
||||
while (match) {
|
||||
const matchText = match[0];
|
||||
|
||||
if (match.index > i) {
|
||||
result.push([text.substring(i, match.index), false]);
|
||||
}
|
||||
|
||||
result.push([matchText, true]);
|
||||
i = match.index + matchText.length;
|
||||
|
||||
match = regexp.exec(text);
|
||||
}
|
||||
|
||||
if (i < text.length) {
|
||||
result.push([text.substring(i, text.length), false]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const urlRegexp = new RegExp("https?://[^ ]+[^ .,]");
|
||||
|
||||
function splitURLs(textNodes) {
|
||||
const text = textNodes.map(n => n.literal).join("");
|
||||
const parts = splitMatches(text, urlRegexp);
|
||||
|
||||
return parts.map(part => {
|
||||
if (part[1]) {
|
||||
return linkNode(part[0], part[0]);
|
||||
} else {
|
||||
return textNode(part[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function transform(parsed) {
|
||||
const walker = parsed.walker();
|
||||
let event;
|
||||
|
||||
let nodes = [];
|
||||
while ((event = walker.next())) {
|
||||
const node = event.node;
|
||||
if (event.entering && node.type === "text") {
|
||||
nodes.push(node);
|
||||
} else {
|
||||
if (nodes.length > 0) {
|
||||
splitURLs(nodes)
|
||||
.reverse()
|
||||
.forEach(newNode => {
|
||||
nodes[0].insertAfter(newNode);
|
||||
});
|
||||
|
||||
nodes.forEach(n => n.unlink());
|
||||
nodes = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nodes.length > 0) {
|
||||
splitURLs(nodes)
|
||||
.reverse()
|
||||
.forEach(newNode => {
|
||||
nodes[0].insertAfter(newNode);
|
||||
});
|
||||
nodes.forEach(n => n.unlink());
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
1
apps/issues/commonmark.min.js
vendored
Normal file
1
apps/issues/commonmark.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
14
apps/issues/index.html
Normal file
14
apps/issues/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="color: #fff">
|
||||
<head>
|
||||
<title>Tilde Friends</title>
|
||||
<base target="_top">
|
||||
</head>
|
||||
<body>
|
||||
<tf-issues-app/>
|
||||
<script>window.litDisableBundleWarning = true;</script>
|
||||
<script src="commonmark.min.js"></script>
|
||||
<script src="commonmark-linkify.js" type="module"></script>
|
||||
<script src="script.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
120
apps/issues/lit-all.min.js
vendored
Normal file
120
apps/issues/lit-all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
apps/issues/lit-all.min.js.map
Normal file
1
apps/issues/lit-all.min.js.map
Normal file
File diff suppressed because one or more lines are too long
260
apps/issues/script.js
Normal file
260
apps/issues/script.js
Normal file
@ -0,0 +1,260 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import * as tfutils from './tf-utils.js';
|
||||
|
||||
const k_project = '%Hr+4xEVtjplidSKBlRWi4Aw/0Tfw7B+1OR9BzlDKmOI=.sha256';
|
||||
|
||||
class TfIdPickerElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
ids: {type: Array},
|
||||
selected: {type: String},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.load();
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.selected = await tfrpc.rpc.localStorageGet('whoami');
|
||||
this.ids = (await tfrpc.rpc.getIdentities()) || [];
|
||||
}
|
||||
|
||||
changed(event) {
|
||||
this.selected = event.srcElement.value;
|
||||
tfrpc.rpc.localStorageSet('whoami', this.selected);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.ids) {
|
||||
return html`
|
||||
<select @change=${this.changed} style="max-width: 100%">
|
||||
${(this.ids).map(id => html`<option ?selected=${id == this.selected} value=${id}>${id}</option>`)}
|
||||
</select>
|
||||
`;
|
||||
} else {
|
||||
return html`<div>Loading...</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('tf-id-picker', TfIdPickerElement);
|
||||
|
||||
class TfComposeElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
value: {type: String},
|
||||
};
|
||||
}
|
||||
|
||||
input() {
|
||||
let input = this.renderRoot.getElementById('input');
|
||||
let preview = this.renderRoot.getElementById('preview');
|
||||
if (input && preview) {
|
||||
preview.innerHTML = tfutils.markdown(input.value);
|
||||
}
|
||||
}
|
||||
|
||||
submit() {
|
||||
this.dispatchEvent(new CustomEvent('tf-submit', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
value: this.renderRoot.getElementById('input').value,
|
||||
},
|
||||
}));
|
||||
this.renderRoot.getElementById('input').value = '';
|
||||
this.input();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<textarea id="input" @input=${this.input} style="flex: 1 1">${this.value}</textarea>
|
||||
<div id="preview" style="flex: 1 1"></div>
|
||||
</div>
|
||||
<input type="submit" value="Submit" @click=${this.submit}></input>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('tf-compose', TfComposeElement);
|
||||
|
||||
class TfIssuesAppElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
issues: {type: Array},
|
||||
selected: {type: Object},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.issues = [];
|
||||
this.load();
|
||||
}
|
||||
|
||||
async load() {
|
||||
let issues = {};
|
||||
let messages = await tfrpc.rpc.query(`
|
||||
WITH issues AS (SELECT messages.* FROM messages_refs JOIN messages ON
|
||||
messages.id = messages_refs.message
|
||||
WHERE messages_refs.ref = ? AND json_extract(messages.content, '$.type') = 'issue'),
|
||||
edits AS (SELECT messages.* FROM issues JOIN messages_refs ON
|
||||
issues.id = messages_refs.ref JOIN messages ON
|
||||
messages.id = messages_refs.message
|
||||
WHERE json_extract(messages.content, '$.type') IN ('issue-edit', 'post'))
|
||||
SELECT * FROM issues
|
||||
UNION
|
||||
SELECT * FROM edits ORDER BY timestamp
|
||||
`, [k_project]);
|
||||
for (let message of messages) {
|
||||
let content = JSON.parse(message.content);
|
||||
switch (content.type) {
|
||||
case 'issue':
|
||||
issues[message.id] = {
|
||||
id: message.id,
|
||||
author: message.author,
|
||||
text: content.text,
|
||||
updates: [],
|
||||
created: message.timestamp,
|
||||
open: true,
|
||||
};
|
||||
break;
|
||||
case 'issue-edit':
|
||||
case 'post':
|
||||
for (let issue of (content.issues || [])) {
|
||||
if (issues[issue.link]) {
|
||||
if (issue.open !== undefined) {
|
||||
issues[issue.link].open = issue.open;
|
||||
message.open = issue.open;
|
||||
}
|
||||
issues[issue.link].updates.push(message);
|
||||
issues[issue.link].updated = message.timestamp;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.issues = Object.values(issues).sort((x, y) => y.created - x.created);
|
||||
if (this.selected) {
|
||||
for (let issue of this.issues) {
|
||||
if (issue.id == this.selected.id) {
|
||||
this.selected = issue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render_issue_table_row(issue) {
|
||||
return html`
|
||||
<tr>
|
||||
<td>${issue.open ? 'open' : 'closed'}</td>
|
||||
<td style="max-width: 8em; overflow: hidden; white-space: nowrap; text-overflow: ellipsis">${issue.author}</td>
|
||||
<td style="max-width: 40em; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; cursor: pointer" @click=${() => this.selected = issue}>
|
||||
${issue.text.split('\n')?.[0]}
|
||||
</td>
|
||||
<td>${new Date(issue.updated ?? issue.created).toLocaleDateString()}</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
render_update(update) {
|
||||
let content = JSON.parse(update.content);
|
||||
let message;
|
||||
if (content.text) {
|
||||
message = unsafeHTML(tfutils.markdown(content.text));
|
||||
}
|
||||
return html`
|
||||
<div style="border-left: 2px solid #fff; padding-left: 8px">
|
||||
<div>${new Date(update.timestamp).toLocaleString()}</div>
|
||||
<div>${update.author}</div>
|
||||
<div>${message}</div>
|
||||
<div>${update.open !== undefined ? (update.open ? 'issue opened' : 'issue closed') : undefined}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async set_open(id, open) {
|
||||
if (confirm(`Are you sure you want to ${open ? 'open' : 'close'} this issue?`)) {
|
||||
let whoami = this.shadowRoot.getElementById('picker').selected;
|
||||
await tfrpc.rpc.appendMessage(whoami, {
|
||||
type: 'issue-edit',
|
||||
issues: [
|
||||
{
|
||||
link: id,
|
||||
open: open,
|
||||
},
|
||||
],
|
||||
});
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
|
||||
async create_issue(event) {
|
||||
let whoami = this.shadowRoot.getElementById('picker').selected;
|
||||
await tfrpc.rpc.appendMessage(whoami, {
|
||||
type: 'issue',
|
||||
project: k_project,
|
||||
text: event.detail.value,
|
||||
});
|
||||
await this.load();
|
||||
}
|
||||
|
||||
async reply_to_issue(event) {
|
||||
let whoami = this.shadowRoot.getElementById('picker').selected;
|
||||
await tfrpc.rpc.appendMessage(whoami, {
|
||||
type: 'post',
|
||||
text: event.detail.value,
|
||||
root: this.selected.id,
|
||||
branch: this.selected.updates.length ? this.selected.updates[this.selected.updates.length - 1].id : this.selected.id,
|
||||
issues: [
|
||||
{
|
||||
link: this.selected.id,
|
||||
},
|
||||
],
|
||||
});
|
||||
await this.load();
|
||||
}
|
||||
|
||||
render() {
|
||||
let header = html`
|
||||
<h1>Tilde Friends Issues</h1>
|
||||
<tf-id-picker id="picker"></tf-id-picker>
|
||||
`;
|
||||
if (this.selected) {
|
||||
return html`
|
||||
${header}
|
||||
<div>
|
||||
<input type="button" value="Back" @click=${() => this.selected = undefined}></input>
|
||||
${this.selected.open ?
|
||||
html`<input type="button" value="Close Issue" @click=${() => this.set_open(this.selected.id, false)}></input>` :
|
||||
html`<input type="button" value="Reopen Issue" @click=${() => this.set_open(this.selected.id, true)}></input>`}
|
||||
</div>
|
||||
<div>${new Date(this.selected.created).toLocaleString()}</div>
|
||||
<div>${this.selected.author}</div>
|
||||
<div>${this.selected.id}</div>
|
||||
<div>${unsafeHTML(tfutils.markdown(this.selected.text))}</div>
|
||||
${this.selected.updates.map(x => this.render_update(x))}
|
||||
<tf-compose @tf-submit=${this.reply_to_issue}></tf-compose>
|
||||
`;
|
||||
} else {
|
||||
return html`
|
||||
${header}
|
||||
<h2>New Issue</h2>
|
||||
<tf-compose @tf-submit=${this.create_issue}></tf-compose>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Author</th>
|
||||
<th>Title</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
${this.issues.map(x => this.render_issue_table_row(x))}
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-issues-app', TfIssuesAppElement);
|
91
apps/issues/tf-utils.js
Normal file
91
apps/issues/tf-utils.js
Normal file
@ -0,0 +1,91 @@
|
||||
import * as linkify from './commonmark-linkify.js';
|
||||
|
||||
function image(node, entering) {
|
||||
if (node.firstChild?.type === 'text' &&
|
||||
node.firstChild.literal.startsWith('video:')) {
|
||||
if (entering) {
|
||||
this.lit('<video style="max-width: 100%; max-height: 480px" title="' + this.esc(node.firstChild?.literal) + '" controls>');
|
||||
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
|
||||
this.disableTags += 1;
|
||||
} else {
|
||||
this.disableTags -= 1;
|
||||
this.lit('</video>');
|
||||
}
|
||||
} else if (node.firstChild?.type === 'text' &&
|
||||
node.firstChild.literal.startsWith('audio:')) {
|
||||
if (entering) {
|
||||
this.lit('<audio style="height: 32px; max-width: 100%" title="' + this.esc(node.firstChild?.literal) + '" controls>');
|
||||
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
|
||||
this.disableTags += 1;
|
||||
} else {
|
||||
this.disableTags -= 1;
|
||||
this.lit('</audio>');
|
||||
}
|
||||
} else {
|
||||
if (entering) {
|
||||
if (this.disableTags === 0) {
|
||||
this.lit('<div class="img_caption">' + this.esc(node.firstChild?.literal || node.destination) + '</div>');
|
||||
if (this.options.safe && potentiallyUnsafe(node.destination)) {
|
||||
this.lit('<img src="" alt="');
|
||||
} else {
|
||||
this.lit('<img src="' + this.esc(node.destination) + '" alt="');
|
||||
}
|
||||
}
|
||||
this.disableTags += 1;
|
||||
} else {
|
||||
this.disableTags -= 1;
|
||||
if (this.disableTags === 0) {
|
||||
if (node.title) {
|
||||
this.lit('" title="' + this.esc(node.title));
|
||||
}
|
||||
this.lit('" />');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markdown(md) {
|
||||
var reader = new commonmark.Parser({safe: true});
|
||||
var writer = new commonmark.HtmlRenderer();
|
||||
writer.image = image;
|
||||
var parsed = reader.parse(md || '');
|
||||
parsed = linkify.transform(parsed);
|
||||
var walker = parsed.walker();
|
||||
var event, node;
|
||||
while ((event = walker.next())) {
|
||||
node = event.node;
|
||||
if (event.entering) {
|
||||
if (node.type == 'link') {
|
||||
if (node.destination.startsWith('@') &&
|
||||
node.destination.endsWith('.ed25519')) {
|
||||
node.destination = '#' + node.destination;
|
||||
} else if (node.destination.startsWith('%') &&
|
||||
node.destination.endsWith('.sha256')) {
|
||||
node.destination = '#' + node.destination;
|
||||
} else if (node.destination.startsWith('&') &&
|
||||
node.destination.endsWith('.sha256')) {
|
||||
node.destination = '/' + node.destination + '/view';
|
||||
}
|
||||
} else if (node.type == 'image') {
|
||||
if (node.destination.startsWith('&')) {
|
||||
node.destination = '/' + node.destination + '/view';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return writer.render(parsed);
|
||||
}
|
||||
|
||||
export function human_readable_size(bytes) {
|
||||
let v = bytes;
|
||||
let u = 'B';
|
||||
for (let unit of ['kB', 'MB', 'GB']) {
|
||||
if (v > 1024) {
|
||||
v /= 1024;
|
||||
u = unit;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return `${Math.round(v * 10) / 10} ${u}`;
|
||||
}
|
48
apps/sneaker/lit-all.min.js
vendored
48
apps/sneaker/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -118,17 +118,23 @@ class TfSneakerAppElement extends LitElement {
|
||||
zip.file(`message/classic/${this.sanitize(id)}.ndjson`, all_messages);
|
||||
|
||||
let blobs = await tfrpc.rpc.query(
|
||||
`SELECT blobs.id
|
||||
`SELECT messages_refs.ref AS id
|
||||
FROM messages
|
||||
JOIN messages_refs ON messages.id = messages_refs.message
|
||||
JOIN blobs ON messages_refs.ref = blobs.id
|
||||
WHERE messages.author = ?`,
|
||||
WHERE messages.author = ? AND messages_refs.ref LIKE '&%.sha256'`,
|
||||
[id]);
|
||||
let blobs_done = 0;
|
||||
for (let row of blobs) {
|
||||
this.progress = {name: 'blobs', value: blobs_done, max: blobs.length};
|
||||
let blob = await tfrpc.rpc.get_blob(row.id);
|
||||
zip.file(`blob/classic/${this.sanitize(row.id)}${this.guess_ext(blob)}`, new Uint8Array(blob));
|
||||
let blob;
|
||||
try {
|
||||
blob = await tfrpc.rpc.get_blob(row.id);
|
||||
} catch (e) {
|
||||
console.log(`Failed to get ${row.id}: ${e.message}`);
|
||||
}
|
||||
if (blob) {
|
||||
zip.file(`blob/classic/${this.sanitize(row.id)}${this.guess_ext(blob)}`, new Uint8Array(blob));
|
||||
}
|
||||
blobs_done++;
|
||||
}
|
||||
|
||||
|
@ -88,6 +88,9 @@ tfrpc.register(function apps() {
|
||||
tfrpc.register(async function try_decrypt(id, content) {
|
||||
return await ssb.privateMessageDecrypt(id, content);
|
||||
});
|
||||
tfrpc.register(async function encrypt(id, recipients, content) {
|
||||
return await ssb.privateMessageEncrypt(id, recipients, content);
|
||||
});
|
||||
ssb.addEventListener('broadcasts', async function() {
|
||||
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
||||
});
|
||||
|
@ -64,7 +64,7 @@ export function picker(callback, anchor) {
|
||||
while (list.firstChild) {
|
||||
list.removeChild(list.firstChild);
|
||||
}
|
||||
let search = input.value;
|
||||
let search = input.value.toLowerCase();
|
||||
let any_at_all = false;
|
||||
for (let row of Object.entries(json)) {
|
||||
let header = document.createElement('div');
|
||||
@ -74,7 +74,7 @@ export function picker(callback, anchor) {
|
||||
for (let entry of Object.entries(row[1])) {
|
||||
if (search &&
|
||||
search.length &&
|
||||
entry[0].indexOf(search) == -1) {
|
||||
entry[0].toLowerCase().indexOf(search) == -1) {
|
||||
continue;
|
||||
}
|
||||
let emoji = document.createElement('span');
|
||||
|
48
apps/ssb/lit-all.min.js
vendored
48
apps/ssb/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -8,7 +8,10 @@ import * as tf_user from './tf-user.js';
|
||||
import * as tf_compose from './tf-compose.js';
|
||||
import * as tf_news from './tf-news.js';
|
||||
import * as tf_profile from './tf-profile.js';
|
||||
import * as tf_tab_mentions from './tf-tab-mentions.js';
|
||||
import * as tf_tab_news from './tf-tab-news.js';
|
||||
import * as tf_tab_news_feed from './tf-tab-news-feed.js';
|
||||
import * as tf_tab_search from './tf-tab-search.js';
|
||||
import * as tf_tab_connections from './tf-tab-connections.js';
|
||||
import * as tf_tab_query from './tf-tab-query.js';
|
||||
import * as tf_tag from './tf-tag.js';
|
@ -16,6 +16,7 @@ class TfElement extends LitElement {
|
||||
following: {type: Array},
|
||||
users: {type: Object},
|
||||
ids: {type: Array},
|
||||
tags: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
@ -32,6 +33,7 @@ class TfElement extends LitElement {
|
||||
this.following = [];
|
||||
this.users = {};
|
||||
this.loaded = false;
|
||||
this.tags = [];
|
||||
tfrpc.rpc.getBroadcasts().then(b => { self.broadcasts = b || []; });
|
||||
tfrpc.rpc.getConnections().then(c => { self.connections = c || []; });
|
||||
tfrpc.rpc.getHash().then(hash => self.set_hash(hash));
|
||||
@ -64,6 +66,10 @@ class TfElement extends LitElement {
|
||||
this.tab = 'search';
|
||||
} else if (this.hash === '#connections') {
|
||||
this.tab = 'connections';
|
||||
} else if (this.hash === '#mentions') {
|
||||
this.tab = 'mentions';
|
||||
} else if (this.hash.startsWith('#sql=')) {
|
||||
this.tab = 'query';
|
||||
} else {
|
||||
this.tab = 'news';
|
||||
}
|
||||
@ -241,22 +247,47 @@ class TfElement extends LitElement {
|
||||
if (confirm("Are you sure you want to create a new identity?")) {
|
||||
await tfrpc.rpc.createIdentity();
|
||||
this.ids = (await tfrpc.rpc.getIdentities()) || [];
|
||||
if (this.ids && !this.whoami) {
|
||||
this.whoami = this.ids[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render_id_picker() {
|
||||
return html`
|
||||
<tf-id-picker id="picker" selected=${this.whoami} .ids=${this.ids} @change=${this._handle_whoami_changed}></tf-id-picker>
|
||||
<button @click=${this.create_identity}>Create Identity</button>
|
||||
<button @click=${this.create_identity} id="create_identity">Create Identity</button>
|
||||
`;
|
||||
}
|
||||
|
||||
async load_recent_tags() {
|
||||
let start = new Date();
|
||||
this.tags = await tfrpc.rpc.query(`
|
||||
WITH
|
||||
recent AS (SELECT id, content FROM messages
|
||||
WHERE messages.timestamp > ? AND json_extract(content, '$.type') = 'post'
|
||||
ORDER BY timestamp DESC LIMIT 1024),
|
||||
recent_channels AS (SELECT recent.id, '#' || json_extract(content, '$.channel') AS tag
|
||||
FROM recent
|
||||
WHERE json_extract(content, '$.channel') IS NOT NULL),
|
||||
recent_mentions AS (SELECT recent.id, json_extract(mention.value, '$.link') AS tag
|
||||
FROM recent, json_each(recent.content, '$.mentions') AS mention
|
||||
WHERE json_valid(mention.value) AND tag LIKE '#%'),
|
||||
combined AS (SELECT id, tag FROM recent_channels UNION ALL SELECT id, tag FROM recent_mentions),
|
||||
by_message AS (SELECT DISTINCT id, tag FROM combined)
|
||||
SELECT tag, COUNT(*) AS count FROM by_message GROUP BY tag ORDER BY count DESC LIMIT 10
|
||||
`, [new Date() - 7 * 24 * 60 * 60 * 1000]);
|
||||
console.log('tags took', (new Date() - start) / 1000.0, 'seconds');
|
||||
}
|
||||
|
||||
async load() {
|
||||
let whoami = this.whoami;
|
||||
let tags = this.load_recent_tags();
|
||||
let [following, users] = await this.following_deep([whoami], 2, {});
|
||||
users = await this.fetch_about(following.sort(), users);
|
||||
this.following = following;
|
||||
this.users = users;
|
||||
await tags;
|
||||
console.log(`load finished ${whoami} => ${this.whoami}`);
|
||||
this.whoami = whoami;
|
||||
this.loaded = whoami;
|
||||
@ -267,16 +298,24 @@ class TfElement extends LitElement {
|
||||
let users = this.users;
|
||||
if (this.tab === 'news') {
|
||||
return html`
|
||||
<tf-tab-news .following=${this.following} whoami=${this.whoami} .users=${this.users} hash=${this.hash} .unread=${this.unread} @refresh=${() => this.unread = []}></tf-tab-news>
|
||||
<tf-tab-news id="tf-tab-news" .following=${this.following} whoami=${this.whoami} .users=${this.users} hash=${this.hash} .unread=${this.unread} @refresh=${() => this.unread = []}></tf-tab-news>
|
||||
`;
|
||||
} else if (this.tab === 'connections') {
|
||||
return html`
|
||||
<tf-tab-connections .users=${this.users} .connections=${this.connections} .broadcasts=${this.broadcasts}></tf-tab-connections>
|
||||
`;
|
||||
} else if (this.tab === 'mentions') {
|
||||
return html`
|
||||
<tf-tab-mentions .following=${this.following} whoami=${this.whoami} .users=${this.users}}></tf-tab-mentions>
|
||||
`;
|
||||
} else if (this.tab === 'search') {
|
||||
return html`
|
||||
<tf-tab-search .following=${this.following} whoami=${this.whoami} .users=${this.users} query=${this.hash?.startsWith('#q=') ? decodeURIComponent(this.hash.substring(3)) : null}></tf-tab-search>
|
||||
`;
|
||||
} else if (this.tab === 'query') {
|
||||
return html`
|
||||
<tf-tab-query .following=${this.following} whoami=${this.whoami} .users=${this.users} query=${this.hash?.startsWith('#sql=') ? decodeURIComponent(this.hash.substring(5)) : null}></tf-tab-query>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,6 +325,10 @@ class TfElement extends LitElement {
|
||||
await tfrpc.rpc.setHash('#');
|
||||
} else if (tab === 'connections') {
|
||||
await tfrpc.rpc.setHash('#connections');
|
||||
} else if (tab === 'mentions') {
|
||||
await tfrpc.rpc.setHash('#mentions');
|
||||
} else if (tab === 'query') {
|
||||
await tfrpc.rpc.setHash('#sql=');
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,7 +336,6 @@ class TfElement extends LitElement {
|
||||
let self = this;
|
||||
|
||||
if (!this.loading && this.whoami && this.loaded !== this.whoami) {
|
||||
console.log(`starting loading ${this.whoami} ${this.loaded}`);
|
||||
this.loading = true;
|
||||
this.load().finally(function() {
|
||||
self.loading = false;
|
||||
@ -304,7 +346,9 @@ class TfElement extends LitElement {
|
||||
<div>
|
||||
<input type="button" class="tab" value="News" ?disabled=${self.tab == 'news'} @click=${() => self.set_tab('news')}></input>
|
||||
<input type="button" class="tab" value="Connections" ?disabled=${self.tab == 'connections'} @click=${() => self.set_tab('connections')}></input>
|
||||
<input type="button" class="tab" value="Mentions" ?disabled=${self.tab == 'mentions'} @click=${() => self.set_tab('mentions')}></input>
|
||||
<input type="button" class="tab" value="Search" ?disabled=${self.tab == 'search'} @click=${() => self.set_tab('search')}></input>
|
||||
<input type="button" class="tab" value="Query" ?disabled=${self.tab == 'query'} @click=${() => self.set_tab('query')}></input>
|
||||
</div>
|
||||
`;
|
||||
let contents =
|
||||
@ -316,9 +360,10 @@ class TfElement extends LitElement {
|
||||
return html`
|
||||
${this.render_id_picker()}
|
||||
${tabs}
|
||||
${this.tags.map(x => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`)}
|
||||
${contents}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-app', TfElement);
|
||||
customElements.define('tf-app', TfElement);
|
||||
|
@ -176,7 +176,7 @@ class TfComposeElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
submit() {
|
||||
async submit() {
|
||||
let self = this;
|
||||
let draft = this.get_draft();
|
||||
let edit = this.renderRoot.getElementById('edit');
|
||||
@ -195,14 +195,25 @@ class TfComposeElement extends LitElement {
|
||||
message.contentWarning = draft.content_warning;
|
||||
}
|
||||
console.log('Would post:', message);
|
||||
tfrpc.rpc.appendMessage(this.whoami, message).then(function() {
|
||||
edit.value = '';
|
||||
self.change();
|
||||
self.notify(undefined);
|
||||
self.requestUpdate();
|
||||
}).catch(function(error) {
|
||||
if (draft.encrypt_to) {
|
||||
let to = new Set(draft.encrypt_to);
|
||||
to.add(this.whoami);
|
||||
to = [...to];
|
||||
message.recps = to;
|
||||
console.log('message is now', message);
|
||||
message = await tfrpc.rpc.encrypt(this.whoami, to, JSON.stringify(message));
|
||||
console.log('encrypted as', message);
|
||||
}
|
||||
try {
|
||||
await tfrpc.rpc.appendMessage(this.whoami, message).then(function() {
|
||||
edit.value = '';
|
||||
self.change();
|
||||
self.notify(undefined);
|
||||
self.requestUpdate();
|
||||
});
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
discard() {
|
||||
@ -226,12 +237,47 @@ class TfComposeElement extends LitElement {
|
||||
input.click();
|
||||
}
|
||||
|
||||
async autocomplete(text, callback) {
|
||||
this.last_autocomplete = text;
|
||||
let results = [];
|
||||
try {
|
||||
let rows = await tfrpc.rpc.query(`
|
||||
SELECT messages.content FROM messages_fts(?)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
WHERE messages.content LIKE ?
|
||||
ORDER BY timestamp DESC LIMIT 10
|
||||
`, ['"' + text.replace('"', '""') + '"', `%%`]);
|
||||
for (let row of rows) {
|
||||
for (let match of row.content.matchAll(/!\[([^\]]*)\]\((&.*?)\)/g)) {
|
||||
if (match[1].toLowerCase().indexOf(text.toLowerCase()) != -1) {
|
||||
results.push({key: match[1], value: match[2]});
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (this.last_autocomplete === text) {
|
||||
callback(results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
let tribute = new Tribute({
|
||||
values: Object.entries(this.users).map(x => ({key: x[1].name, value: x[0]})),
|
||||
selectTemplate: function(item) {
|
||||
return `[@${item.original.key}](${item.original.value})`;
|
||||
},
|
||||
collection: [
|
||||
{
|
||||
values: Object.entries(this.users).map(x => ({key: x[1].name, value: x[0]})),
|
||||
selectTemplate: function(item) {
|
||||
return `[@${item.original.key}](${item.original.value})`;
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: '&',
|
||||
values: this.autocomplete,
|
||||
selectTemplate: function(item) {
|
||||
return ``;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
tribute.attach(this.renderRoot.getElementById('edit'));
|
||||
}
|
||||
@ -244,6 +290,16 @@ class TfComposeElement extends LitElement {
|
||||
preview.innerHTML = this.process_text(edit.value);
|
||||
this.last_updated_text = edit.value;
|
||||
}
|
||||
let encrypt = this.renderRoot.getElementById('encrypt_to');
|
||||
if (encrypt) {
|
||||
let tribute = new Tribute({
|
||||
values: Object.entries(this.users).map(x => ({key: x[1].name, value: x[0]})),
|
||||
selectTemplate: function(item) {
|
||||
return item.original.value;
|
||||
},
|
||||
});
|
||||
tribute.attach(encrypt);
|
||||
}
|
||||
}
|
||||
|
||||
remove_mention(id) {
|
||||
@ -354,6 +410,45 @@ class TfComposeElement extends LitElement {
|
||||
return this.drafts[this.branch || ''] || {};
|
||||
}
|
||||
|
||||
update_encrypt(event) {
|
||||
let input = event.srcElement;
|
||||
let matches = input.value.match(/@.*?\.ed25519/g);
|
||||
if (matches) {
|
||||
let draft = this.get_draft();
|
||||
let to = [...new Set(matches.concat(draft.encrypt_to))];
|
||||
this.set_encrypt(to);
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
render_encrypt() {
|
||||
let draft = this.get_draft();
|
||||
if (draft.encrypt_to === undefined) {
|
||||
return;
|
||||
}
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row; width: 100%">
|
||||
<label for="encrypt_to">🔐 To:</label>
|
||||
<input type="text" id="encrypt_to" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
|
||||
<input type="button" value="🚮" @click=${() => this.set_encrypt(undefined)}></input>
|
||||
</div>
|
||||
<ul>
|
||||
${draft.encrypt_to.map(x => html`
|
||||
<li>
|
||||
<tf-user id=${x} .users=${this.users}></tf-user>
|
||||
<input type="button" value="🚮" @click=${() => this.set_encrypt(draft.encrypt_to.filter(id => id != x))}></input>
|
||||
</li>`)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
set_encrypt(encrypt) {
|
||||
let draft = this.get_draft();
|
||||
draft.encrypt_to = encrypt;
|
||||
this.notify(draft);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
let draft = self.get_draft();
|
||||
@ -361,7 +456,11 @@ class TfComposeElement extends LitElement {
|
||||
draft.content_warning !== undefined ?
|
||||
html`<div id="content_warning_preview" class="content_warning">${draft.content_warning}</div>` :
|
||||
undefined;
|
||||
let encrypt = draft.encrypt_to !== undefined ?
|
||||
undefined :
|
||||
html`<input type="button" value="🔐" @click=${() => this.set_encrypt([])}></input>`;
|
||||
let result = html`
|
||||
${this.render_encrypt()}
|
||||
<div style="display: flex; flex-direction: row; width: 100%">
|
||||
<textarea id="edit" @input=${this.input} @change=${this.change} @paste=${this.paste} style="flex: 1 0 50%">${draft.text}</textarea>
|
||||
<div style="flex: 1 0 50%">
|
||||
@ -372,13 +471,14 @@ class TfComposeElement extends LitElement {
|
||||
${Object.values(draft.mentions || {}).map(x => self.render_mention(x))}
|
||||
${this.render_content_warning()}
|
||||
${this.render_attach_app()}
|
||||
<input type="button" value="Submit" @click=${this.submit}></input>
|
||||
<input type="button" id="submit" value="Submit" @click=${this.submit}></input>
|
||||
<input type="button" value="Attach" @click=${this.attach}></input>
|
||||
${this.render_attach_app_button()}
|
||||
${encrypt}
|
||||
<input type="button" value="Discard" @click=${this.discard}></input>
|
||||
`;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-compose', TfComposeElement);
|
||||
customElements.define('tf-compose', TfComposeElement);
|
||||
|
@ -14,7 +14,6 @@ class TfIdentityPickerElement extends LitElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
let self = this;
|
||||
this.ids = [];
|
||||
}
|
||||
|
||||
@ -34,4 +33,4 @@ class TfIdentityPickerElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-id-picker', TfIdentityPickerElement);
|
||||
customElements.define('tf-id-picker', TfIdentityPickerElement);
|
||||
|
@ -11,10 +11,9 @@ class TfMessageElement extends LitElement {
|
||||
message: {type: Object},
|
||||
users: {type: Object},
|
||||
drafts: {type: Object},
|
||||
raw: {type: Boolean},
|
||||
format: {type: String},
|
||||
blog_data: {type: String},
|
||||
expanded: {type: Object},
|
||||
decrypted: {type: Object},
|
||||
};
|
||||
}
|
||||
|
||||
@ -27,13 +26,14 @@ class TfMessageElement extends LitElement {
|
||||
this.message = {};
|
||||
this.users = {};
|
||||
this.drafts = {};
|
||||
this.raw = false;
|
||||
this.format = 'message';
|
||||
this.expanded = {};
|
||||
this.decrypted = undefined;
|
||||
}
|
||||
|
||||
show_reply() {
|
||||
let event = new CustomEvent('tf-draft', {bubbles: true, composed: true, detail: {id: this.message?.id, draft: ''}});
|
||||
let event = new CustomEvent('tf-draft', {bubbles: true, composed: true, detail: {id: this.message?.id, draft: {
|
||||
encrypt_to: this.message?.decrypted?.recps,
|
||||
}}});
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
|
||||
@ -220,31 +220,56 @@ class TfMessageElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
async try_decrypt(content) {
|
||||
let result = await tfrpc.rpc.try_decrypt(this.whoami, content);
|
||||
if (result) {
|
||||
this.decrypted = JSON.parse(result);
|
||||
} else {
|
||||
this.decrypted = false;
|
||||
render_channels() {
|
||||
let content = this.message?.content;
|
||||
if (this?.messsage?.decrypted?.type == 'post') {
|
||||
content = this.message.decrypted;
|
||||
}
|
||||
let channels = [];
|
||||
if (typeof content.channel === 'string') {
|
||||
channels.push(`#${content.channel}`);
|
||||
}
|
||||
if (Array.isArray(content.mentions)) {
|
||||
for (let mention of content.mentions) {
|
||||
if (typeof mention?.link === 'string' &&
|
||||
mention.link.startsWith('#')) {
|
||||
channels.push(mention.link);
|
||||
}
|
||||
}
|
||||
}
|
||||
return channels.map(x => html`<tf-tag tag=${x}></tf-tag>`);
|
||||
}
|
||||
|
||||
render() {
|
||||
let content = this.message?.content;
|
||||
if (this.decrypted?.type == 'post') {
|
||||
content = this.decrypted;
|
||||
if (this.message?.decrypted?.type == 'post') {
|
||||
content = this.message.decrypted;
|
||||
}
|
||||
let self = this;
|
||||
let raw_button = this.raw ?
|
||||
html`<input type="button" value="Message" @click=${() => self.raw = false}></input>` :
|
||||
html`<input type="button" value="Raw" @click=${() => self.raw = true}></input>`;
|
||||
let raw_button;
|
||||
switch (this.format) {
|
||||
case 'raw':
|
||||
if (content?.type == 'post' || content?.type == 'blog') {
|
||||
raw_button = html`<input type="button" value="Markdown" @click=${() => self.format = 'md'}></input>`;
|
||||
} else {
|
||||
raw_button = html`<input type="button" value="Message" @click=${() => self.format = 'message'}></input>`;
|
||||
}
|
||||
break;
|
||||
case 'md':
|
||||
raw_button = html`<input type="button" value="Message" @click=${() => self.format = 'message'}></input>`;
|
||||
break;
|
||||
default:
|
||||
raw_button = html`<input type="button" value="Raw" @click=${() => self.format = 'raw'}></input>`;
|
||||
break;
|
||||
}
|
||||
function small_frame(inner) {
|
||||
let body;
|
||||
return html`
|
||||
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere">
|
||||
<tf-user id=${self.message.author} .users=${self.users}></tf-user>
|
||||
<span style="padding-right: 8px"><a tfarget="_top" href=${'#' + self.message.id}>%</a> ${new Date(self.message.timestamp).toLocaleString()}</span>
|
||||
${raw_button}
|
||||
${self.raw ? self.render_raw() : inner}
|
||||
${self.format == 'raw' ? self.render_raw() : inner}
|
||||
${self.render_votes()}
|
||||
</div>
|
||||
`;
|
||||
@ -322,14 +347,24 @@ class TfMessageElement extends LitElement {
|
||||
<input type="button" value="Reply" @click=${this.show_reply}></input>
|
||||
`;
|
||||
let self = this;
|
||||
let body = this.raw ?
|
||||
this.render_raw() :
|
||||
unsafeHTML(tfutils.markdown(content.text));
|
||||
let body;
|
||||
switch (this.format) {
|
||||
case 'raw':
|
||||
body = this.render_raw();
|
||||
break;
|
||||
case 'md':
|
||||
body = html`<code style="white-space: pre-wrap; overflow-wrap: anywhere">${content.text}</code>`;
|
||||
break;
|
||||
case 'message':
|
||||
body = unsafeHTML(tfutils.markdown(content.text));
|
||||
break;
|
||||
}
|
||||
let content_warning = html`
|
||||
<div class="content_warning" @click=${x => this.toggle_expanded(':cw')}>${content.contentWarning}</div>
|
||||
`;
|
||||
let content_html =
|
||||
html`
|
||||
${this.render_channels()}
|
||||
<div @click=${this.body_click}>${body}</div>
|
||||
${this.render_mentions()}
|
||||
`;
|
||||
@ -342,8 +377,8 @@ class TfMessageElement extends LitElement {
|
||||
` :
|
||||
content_warning :
|
||||
content_html;
|
||||
let is_encrypted = this.decrypted ? html`<span style="align-self: center">🔓</span>` : undefined;
|
||||
let style_background = this.decrypted ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.1)';
|
||||
let is_encrypted = this.message?.decrypted ? html`<span style="align-self: center">🔓</span>` : undefined;
|
||||
let style_background = this.message?.decrypted ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.1)';
|
||||
return html`
|
||||
<style>
|
||||
code {
|
||||
@ -376,6 +411,40 @@ class TfMessageElement extends LitElement {
|
||||
${this.render_children()}
|
||||
</div>
|
||||
`;
|
||||
} else if (content.type === 'issue') {
|
||||
let is_encrypted = this.message?.decrypted ? html`<span style="align-self: center">🔓</span>` : undefined;
|
||||
let style_background = this.message?.decrypted ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.1)';
|
||||
return html`
|
||||
<style>
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
div {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<div style="border: 1px solid black; background-color: ${style_background}; margin-top: 8px; padding: 16px">
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
|
||||
${is_encrypted}
|
||||
<span style="flex: 1"></span>
|
||||
<span style="padding-right: 8px"><a target="_top" href=${'#' + self.message.id}>%</a> ${new Date(this.message.timestamp).toLocaleString()}</span>
|
||||
<span>${raw_button}</span>
|
||||
</div>
|
||||
${content.text}
|
||||
${this.render_votes()}
|
||||
<div>
|
||||
<input type="button" value="React" @click=${this.react}></input>
|
||||
</div>
|
||||
${this.render_children()}
|
||||
</div>
|
||||
`;
|
||||
} else if (content.type === 'blog') {
|
||||
let self = this;
|
||||
tfrpc.rpc.get_blob(content.blog).then(function(data) {
|
||||
@ -385,9 +454,16 @@ class TfMessageElement extends LitElement {
|
||||
this.expanded[(this.message.id || '') + ':blog'] ?
|
||||
html`<div>${this.blog_data ? unsafeHTML(tfutils.markdown(this.blog_data)) : 'Loading...'}</div>` :
|
||||
undefined;
|
||||
let body = this.raw ?
|
||||
this.render_raw() :
|
||||
html`
|
||||
let body;
|
||||
switch (this.format) {
|
||||
case 'raw':
|
||||
body = this.render_raw();
|
||||
break;
|
||||
case 'md':
|
||||
body = content.summary;
|
||||
break;
|
||||
case 'message':
|
||||
body = html`
|
||||
<div
|
||||
style="border: 1px solid #fff; border-radius: 1em; padding: 8px; margin: 4px; cursor: pointer"
|
||||
@click=${x => self.toggle_expanded(':blog')}>
|
||||
@ -399,6 +475,8 @@ class TfMessageElement extends LitElement {
|
||||
</div>
|
||||
${payload}
|
||||
`;
|
||||
break;
|
||||
}
|
||||
return html`
|
||||
<style>
|
||||
code {
|
||||
@ -447,13 +525,10 @@ class TfMessageElement extends LitElement {
|
||||
</div>
|
||||
`);
|
||||
} else if (typeof(this.message.content) == 'string') {
|
||||
if (this.decrypted) {
|
||||
if (this.message?.decrypted) {
|
||||
return small_frame(html`<span>🔓</span><pre>${JSON.stringify(this.decrypted, null, 2)}</pre>`);
|
||||
} else if (this.decrypted === undefined) {
|
||||
this.try_decrypt(content);
|
||||
return small_frame(html`<span>🔐</span>`);
|
||||
} else {
|
||||
return small_frame(html`<span>🔒</span>`);
|
||||
return small_frame(html`<span>🔒</span>`);
|
||||
}
|
||||
} else {
|
||||
return small_frame(html`<div><b>type</b>: ${content.type}</div>`);
|
||||
|
@ -45,4 +45,10 @@ div.img_caption {
|
||||
div.img_caption::after {
|
||||
content: ' ±';
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #fff;
|
||||
margin-left: 0px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
`;
|
@ -42,11 +42,8 @@ class TfTabConnectionsElement extends LitElement {
|
||||
let self = this;
|
||||
let peers = this.broadcasts.filter(x => x.tunnel?.id == connection);
|
||||
if (peers.length) {
|
||||
return html`
|
||||
<ul>
|
||||
${peers.map(x => html`${self.render_room_peer(x)}`)}
|
||||
</ul>
|
||||
`;
|
||||
let connections = this.connections.map(x => x.id);
|
||||
return html`${peers.filter(x => connections.indexOf(x.pubkey) == -1).map(x => html`${self.render_room_peer(x)}`)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +56,7 @@ class TfTabConnectionsElement extends LitElement {
|
||||
return html`
|
||||
<li>
|
||||
<input type="button" @click=${() => self._tunnel(connection.tunnel.id, connection.pubkey)} value="Connect"></input>
|
||||
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
|
||||
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user> 📡
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
@ -79,6 +76,18 @@ class TfTabConnectionsElement extends LitElement {
|
||||
this.stored_connections = (await tfrpc.rpc.getStoredConnections()) || [];
|
||||
}
|
||||
|
||||
render_connection(connection) {
|
||||
return html`
|
||||
<input type="button" @click=${() => tfrpc.rpc.closeConnection(connection.id)} value="Close"></input>
|
||||
<tf-user id=${connection.id} .users=${this.users}></tf-user>
|
||||
${connection.tunnel !== undefined ? '🚇' : html`(${connection.host}:${connection.port})`}
|
||||
<ul>
|
||||
${this.connections.filter(x => x.tunnel === this.connections.indexOf(connection)).map(x => html`<li>${this.render_connection(x)}</li>`)}
|
||||
${this.render_room_peers(connection.id)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
return html`
|
||||
@ -93,12 +102,8 @@ class TfTabConnectionsElement extends LitElement {
|
||||
</ul>
|
||||
<h2>Connections</h2>
|
||||
<ul>
|
||||
${this.connections.map(x => html`
|
||||
<li>
|
||||
<input type="button" @click=${() => tfrpc.rpc.closeConnection(x)} value="Close"></input>
|
||||
<tf-user id=${x} .users=${this.users}></tf-user>
|
||||
${self.render_room_peers(x)}
|
||||
</li>
|
||||
${this.connections.filter(x => x.tunnel === undefined).map(x => html`
|
||||
<li>${this.render_connection(x)}</li>
|
||||
`)}
|
||||
</ul>
|
||||
<h2>Stored Connections (WIP)</h2>
|
||||
|
65
apps/ssb/tf-tab-mentions.js
Normal file
65
apps/ssb/tf-tab-mentions.js
Normal file
@ -0,0 +1,65 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
|
||||
class TfTabMentionsElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
whoami: {type: String},
|
||||
users: {type: Object},
|
||||
following: {type: Array},
|
||||
expanded: {type: Object},
|
||||
messages: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
let self = this;
|
||||
this.whoami = null;
|
||||
this.users = {};
|
||||
this.following = [];
|
||||
this.expanded = {};
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
async load() {
|
||||
console.log('Loading...', this.whoami);
|
||||
let results = await tfrpc.rpc.query(`
|
||||
SELECT messages.*
|
||||
FROM messages_fts(?)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.author != ?
|
||||
ORDER BY timestamp DESC limit 20
|
||||
`,
|
||||
['"' + this.whoami.replace('"', '""') + '"', JSON.stringify(this.following), this.whoami]);
|
||||
console.log('Done.');
|
||||
this.messages = results;
|
||||
}
|
||||
|
||||
on_expand(event) {
|
||||
if (event.detail.expanded) {
|
||||
let expand = {};
|
||||
expand[event.detail.id] = true;
|
||||
this.expanded = Object.assign({}, this.expanded, expand);
|
||||
} else {
|
||||
delete this.expanded[event.detail.id];
|
||||
this.expanded = Object.assign({}, this.expanded);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
if (!this.loading) {
|
||||
this.loading = true;
|
||||
this.load();
|
||||
}
|
||||
return html`
|
||||
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('tf-tab-mentions', TfTabMentionsElement);
|
@ -70,7 +70,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
WITH news AS (SELECT messages.*
|
||||
FROM messages
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.timestamp > ?
|
||||
WHERE messages.timestamp > ? AND messages.timestamp < ?
|
||||
ORDER BY messages.timestamp DESC)
|
||||
SELECT messages.*
|
||||
FROM news
|
||||
@ -87,6 +87,11 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
[
|
||||
JSON.stringify(this.following),
|
||||
this.start_time,
|
||||
/*
|
||||
** Don't show messages more than a day into the future to prevent
|
||||
** messages with far-future timestamps from staying at the top forever.
|
||||
*/
|
||||
new Date().valueOf() + 24 * 60 * 60 * 1000,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -119,7 +124,38 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
this.start_time,
|
||||
last_start_time,
|
||||
]);
|
||||
this.messages = [...more, ...this.messages];
|
||||
this.messages = await this.decrypt([...more, ...this.messages]);
|
||||
}
|
||||
|
||||
async decrypt(messages) {
|
||||
let result = [];
|
||||
for (let message of messages) {
|
||||
let content;
|
||||
try {
|
||||
content = JSON.parse(message?.content);
|
||||
} catch {
|
||||
}
|
||||
if (typeof(content) === 'string') {
|
||||
let decrypted;
|
||||
try {
|
||||
decrypted = await tfrpc.rpc.try_decrypt(this.whoami, content);
|
||||
} catch {
|
||||
}
|
||||
if (decrypted) {
|
||||
try {
|
||||
message.decrypted = JSON.parse(decrypted);
|
||||
} catch {
|
||||
message.decrypted = decrypted;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push(message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async add_messages(messages) {
|
||||
this.messages = await this.decrypt([...messages, ...this.messages]);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -131,7 +167,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
this.messages = [];
|
||||
this._messages_hash = this.hash;
|
||||
this._messages_following = this.following;
|
||||
this.fetch_messages().then(function(messages) {
|
||||
this.fetch_messages().then(this.decrypt.bind(this)).then(function(messages) {
|
||||
self.messages = messages;
|
||||
console.log(`loading mesages done for ${self.whoami}`);
|
||||
}).catch(function(error) {
|
||||
|
@ -48,7 +48,7 @@ class TfTabNewsElement extends LitElement {
|
||||
let news = this.shadowRoot?.getElementById('news');
|
||||
if (news) {
|
||||
console.log('injecting messages', news.messages);
|
||||
news.messages = Object.values(Object.fromEntries([...this.unread, ...news.messages].map(x => [x.id, x])));
|
||||
news.add_messages(Object.values(Object.fromEntries(this.unread.map(x => [x.id, x]))));
|
||||
this.dispatchEvent(new CustomEvent('refresh'));
|
||||
}
|
||||
}
|
||||
@ -109,11 +109,11 @@ class TfTabNewsElement extends LitElement {
|
||||
<div><input type="button" value=${this.new_messages_text()} @click=${this.show_more}></input></div>
|
||||
<a target="_top" href="#" ?hidden=${this.hash.length <= 1}>🏠Home</a>
|
||||
<div>Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!</div>
|
||||
<div><tf-compose whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} @tf-draft=${this.draft}></tf-compose></div>
|
||||
<div><tf-compose id="tf-compose" whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} @tf-draft=${this.draft}></tf-compose></div>
|
||||
${profile}
|
||||
<tf-tab-news-feed id="news" whoami=${this.whoami} .users=${this.users} .following=${this.following} hash=${this.hash} .drafts=${this.drafts} .expanded=${this.expanded} @tf-draft=${this.draft} @tf-expand=${this.on_expand}></tf-tab-news-feed>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-tab-news', TfTabNewsElement);
|
||||
customElements.define('tf-tab-news', TfTabNewsElement);
|
||||
|
114
apps/ssb/tf-tab-query.js
Normal file
114
apps/ssb/tf-tab-query.js
Normal file
@ -0,0 +1,114 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
|
||||
class TfTabQueryElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
whoami: {type: String},
|
||||
users: {type: Object},
|
||||
following: {type: Array},
|
||||
query: {type: String},
|
||||
expanded: {type: Object},
|
||||
results: {type: Array},
|
||||
error: {type: Object},
|
||||
duration: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
let self = this;
|
||||
this.whoami = null;
|
||||
this.users = {};
|
||||
this.following = [];
|
||||
this.expanded = {};
|
||||
this.duration = undefined;
|
||||
}
|
||||
|
||||
async search(query) {
|
||||
console.log('Searching...', this.whoami, query);
|
||||
this.results = [];
|
||||
this.error = undefined;
|
||||
this.duration = undefined;
|
||||
let search = this.renderRoot.getElementById('search');
|
||||
if (search) {
|
||||
search.value = query;
|
||||
search.focus();
|
||||
}
|
||||
await tfrpc.rpc.setHash('#sql=' + encodeURIComponent(query));
|
||||
let start_time = new Date();
|
||||
try {
|
||||
this.results = await tfrpc.rpc.query(query, [])
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
}
|
||||
let end_time = new Date();
|
||||
this.duration = (end_time - start_time).valueOf();
|
||||
console.log('Done.');
|
||||
search = this.renderRoot.getElementById('search');
|
||||
if (search) {
|
||||
search.value = query;
|
||||
search.focus();
|
||||
}
|
||||
}
|
||||
|
||||
search_keydown(event) {
|
||||
if (event.keyCode == 13 && event.ctrlKey) {
|
||||
this.query = this.renderRoot.getElementById('search').value;
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
on_expand(event) {
|
||||
if (event.detail.expanded) {
|
||||
let expand = {};
|
||||
expand[event.detail.id] = true;
|
||||
this.expanded = Object.assign({}, this.expanded, expand);
|
||||
} else {
|
||||
delete this.expanded[event.detail.id];
|
||||
this.expanded = Object.assign({}, this.expanded);
|
||||
}
|
||||
}
|
||||
|
||||
render_results() {
|
||||
if (!this.results?.length) {
|
||||
return html`<div>No results.</div>`;
|
||||
} else {
|
||||
let keys = Object.keys(this.results[0]).sort();
|
||||
return html`<table style="width: 100%; max-width: 100%">
|
||||
<tr>${keys.map(key => html`<th>${key}</th>`)}</tr>
|
||||
${this.results.map(row => html`<tr>${keys.map(key => html`<td>${row[key]}</td>`)}</tr>`)}
|
||||
</table>`;
|
||||
}
|
||||
}
|
||||
|
||||
render_error() {
|
||||
if (this.error) {
|
||||
return html`<h2 style="color: red">${this.error.message}</h2>
|
||||
<pre style="color: red">${this.error.stack}</pre>`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.query !== this.last_query) {
|
||||
this.last_query = this.query;
|
||||
this.search(this.query);
|
||||
}
|
||||
let self = this;
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<textarea id="search" rows=8 style="flex: 1" @keydown=${this.search_keydown}>${this.query}</textarea>
|
||||
<input type="button" value="Execute" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}></input>
|
||||
</div>
|
||||
<div ?hidden=${this.duration === undefined}>Took ${this.duration / 1000.0} seconds.</div>
|
||||
<div ?hidden=${this.duration !== undefined}>Executing...</div>
|
||||
${this.render_error()}
|
||||
${this.render_results()}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-tab-query', TfTabQueryElement);
|
@ -9,6 +9,7 @@ class TfTabSearchElement extends LitElement {
|
||||
users: {type: Object},
|
||||
following: {type: Array},
|
||||
query: {type: String},
|
||||
expanded: {type: Object},
|
||||
};
|
||||
}
|
||||
|
||||
@ -20,6 +21,7 @@ class TfTabSearchElement extends LitElement {
|
||||
this.whoami = null;
|
||||
this.users = {};
|
||||
this.following = [];
|
||||
this.expanded = {};
|
||||
}
|
||||
|
||||
async search(query) {
|
||||
@ -55,8 +57,20 @@ class TfTabSearchElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
on_expand(event) {
|
||||
if (event.detail.expanded) {
|
||||
let expand = {};
|
||||
expand[event.detail.id] = true;
|
||||
this.expanded = Object.assign({}, this.expanded, expand);
|
||||
} else {
|
||||
delete this.expanded[event.detail.id];
|
||||
this.expanded = Object.assign({}, this.expanded);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.query !== this.last_query) {
|
||||
this.last_query = this.query;
|
||||
this.search(this.query);
|
||||
}
|
||||
let self = this;
|
||||
@ -65,7 +79,7 @@ class TfTabSearchElement extends LitElement {
|
||||
<input type="text" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
|
||||
<input type="button" value="Search" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}></input>
|
||||
</div>
|
||||
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users}></tf-news>
|
||||
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
24
apps/ssb/tf-tag.js
Normal file
24
apps/ssb/tf-tag.js
Normal file
@ -0,0 +1,24 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
|
||||
class TfTagElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
tag: {type: String},
|
||||
count: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
render() {
|
||||
let number = this.count ? html` (${this.count})` : undefined;
|
||||
return html`<a href="#q=${this.tag}" style="display: inline-block; margin: 3px; border: 1px solid black; background-color: #444; padding: 4px; border-radius: 3px">${this.tag}${number}</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-tag', TfTagElement);
|
@ -119,7 +119,7 @@ class TodoListElement extends LitElement {
|
||||
} else {
|
||||
return html`
|
||||
<div><input type="checkbox" ?checked=${item.x} @change=${x => self.handle_check(x, item)}></input>
|
||||
<span @click=${x => self.editing = index}>${item.text}</span>
|
||||
<span @click=${x => self.editing = index}>${item.text || '(empty)'}</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
14
core/app.js
14
core/app.js
@ -61,8 +61,7 @@ function socket(request, response, client) {
|
||||
let process;
|
||||
let options = {};
|
||||
let credentials = auth.query(request.headers);
|
||||
let refresh_token = credentials?.refresh?.token;
|
||||
let refresh_interval = credentials?.refresh?.interval;
|
||||
let refresh = auth.make_refresh(credentials);
|
||||
|
||||
response.onClose = async function() {
|
||||
if (process && process.task) {
|
||||
@ -125,9 +124,14 @@ function socket(request, response, client) {
|
||||
options.credentials = credentials;
|
||||
options.packageOwner = packageOwner;
|
||||
options.packageName = packageName;
|
||||
options.url = message.url;
|
||||
let sessionId = makeSessionId();
|
||||
if (blobId) {
|
||||
process = await core.getSessionProcessBlob(blobId, sessionId, options);
|
||||
if (message.edit_only) {
|
||||
response.send(JSON.stringify({action: 'ready', edit_only: true}), 0x1);
|
||||
} else {
|
||||
process = await core.getSessionProcessBlob(blobId, sessionId, options);
|
||||
}
|
||||
}
|
||||
if (process) {
|
||||
process.app.readOutput(function(message) {
|
||||
@ -198,9 +202,9 @@ function socket(request, response, client) {
|
||||
}
|
||||
}
|
||||
|
||||
if (refresh_token) {
|
||||
if (refresh) {
|
||||
return {
|
||||
'Set-Cookie': `session=${refresh_token}; path=/; Max-Age=${refresh_interval}; Secure; SameSite=Strict`,
|
||||
'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
133
core/auth.html
133
core/auth.html
@ -2,18 +2,137 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Tilde Friends Sign-in</title>
|
||||
<script>
|
||||
function showHideConfirm() {
|
||||
document.getElementById("confirmPassword").style.display = document.getElementById("register").checked ? "block" : "none";
|
||||
}
|
||||
</script>
|
||||
<link type="text/css" rel="stylesheet" href="/static/style.css">
|
||||
<link type="image/png" rel="shortcut icon" href="/static/favicon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!--HEAD-->
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="text-align: center">Tilde Friends Sign-in</h1>
|
||||
<div id="content"><!--SESSION--></div>
|
||||
<tf-auth id="auth"></tf-auth>
|
||||
<script>window.litDisableBundleWarning = true;</script>
|
||||
<script type="module">
|
||||
import {LitElement, html} from '/static/lit/lit-all.min.js';
|
||||
let g_data = $AUTH_DATA;
|
||||
let app = document.getElementById('auth');
|
||||
Object.assign(app, g_data);
|
||||
|
||||
class TfAuthElement extends LitElement {
|
||||
static get_properties() {
|
||||
return {
|
||||
name: {type: String},
|
||||
tab: {type: String},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.tab = 'login';
|
||||
}
|
||||
|
||||
tab_changed(name) {
|
||||
this.tab = name;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
return html`
|
||||
<style>
|
||||
[name="tab"] {
|
||||
display: none;
|
||||
}
|
||||
[name="tab"]+label {
|
||||
background-color: #586e75;
|
||||
padding: 1em;
|
||||
display: inline-block;
|
||||
flex: 1 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
[name="tab"]+label:hover {
|
||||
background-color: #dc322f;
|
||||
}
|
||||
[name="tab"]:checked+label {
|
||||
background-color: #93a1a1;
|
||||
border: 2px solid #eee8d5;
|
||||
}
|
||||
.error {
|
||||
color: #f00;
|
||||
border: 1px solid #f00;
|
||||
margin: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
form label {
|
||||
padding-top: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
form input {
|
||||
font-size: x-large;
|
||||
padding: 4px;
|
||||
}
|
||||
input[type="submit"] {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
padding: 1em;
|
||||
}
|
||||
</style>
|
||||
<div style="display: flex; flex-direction: column; max-width: 1280px; margin: auto">
|
||||
<h1 ?hidden=${this.name}>Welcome.</h1>
|
||||
<h1 ?hidden=${this.name === undefined}>Welcome, ${this.name}.</h1>
|
||||
|
||||
<div style="display: flex; flex-direction: row; width: 100%">
|
||||
<input type="radio" name="tab" id="login" value="Login" ?checked=${this.tab == 'login'} @change=${() => self.tab_changed('login')}></input>
|
||||
<label for="login" id="login_label">Login</label>
|
||||
|
||||
<input type="radio" name="tab" id="register" value="Register" ?checked=${this.tab == 'register'} @change=${() => self.tab_changed('register')}></input>
|
||||
<label for="register" id="register_label">Register</label>
|
||||
|
||||
<input type="radio" name="tab" id="guest" value="Guest" ?checked=${this.tab == 'guest'} @change=${() => self.tab_changed('guest')}></input>
|
||||
<label for="guest" id="guest_label">Guest</label>
|
||||
|
||||
<input type="radio" name="tab" id="change" value="Change Password" ?checked=${this.tab == 'change'} @change=${() => self.tab_changed('change')}></input>
|
||||
<label for="change" id="change_label">Change Password</label>
|
||||
</div>
|
||||
|
||||
<div ?hidden=${this.tab != 'login' && this.tab != 'register' && this.tab != 'change'}>
|
||||
<div id="error" ?hidden=${this.error === undefined} class="error">
|
||||
${this.error}
|
||||
</div>
|
||||
<form method="POST" style="display: flex; flex-direction: column">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name"></input>
|
||||
|
||||
<label for="password">${this.tab == 'change' ? 'Old ' : ''}Password:</label>
|
||||
<input type="password" id="password" name="password"></input>
|
||||
|
||||
<label ?hidden=${this.tab != 'change'} for="new_password">New Password:</label>
|
||||
<input ?hidden=${this.tab != 'change'} type="password" id="new_password" name="new_password"></input>
|
||||
|
||||
<label ?hidden=${this.tab != 'register' && this.tab != 'change'} for="confirm">Confirm ${this.tab == 'change' ? 'New ' : ''}Password:</label>
|
||||
<input ?hidden=${this.tab != 'register' && this.tab != 'change'} type="password" id="confirm" name="confirm"></input>
|
||||
|
||||
<input id="loginButton" type="submit" name="submit" value="Login"></input>
|
||||
<input type="hidden" name="register" value="${this.tab == 'register' ? 1 : 0}"></input>
|
||||
<input type="hidden" name="change" value="${this.tab == 'change' ? 1 : 0}"></input>
|
||||
</form>
|
||||
</div>
|
||||
<div ?hidden=${this.tab != 'guest'}>
|
||||
<form method="POST" style="display: flex; flex-direction: column">
|
||||
<input type="submit" id="guestButton" name="guestButton" value="Proceed as Guest"></input>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div ?hidden=${this.have_administrator} class="notice">
|
||||
There is currently no administrator. You will be made administrator.
|
||||
</div>
|
||||
|
||||
<h2>Code of Conduct</h2>
|
||||
<textarea readonly rows="20" cols="80" style="resize: none">${this.code_of_conduct}</textarea>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('tf-auth', TfAuthElement);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
103
core/auth.js
103
core/auth.js
@ -113,22 +113,37 @@ function getCookies(headers) {
|
||||
return cookies;
|
||||
}
|
||||
|
||||
function isNameValid(name) {
|
||||
let c = name.charAt(0);
|
||||
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) && name.split().map(x => x >= ('a' && x <= 'z') || x >= ('A' && x <= 'Z') || x >= ('0' && x <= '9'));
|
||||
}
|
||||
|
||||
function handler(request, response) {
|
||||
let session = getCookies(request.headers).session;
|
||||
if (request.uri == "/login") {
|
||||
let formData = form.decodeForm(request.query);
|
||||
if (query(request.headers)?.permissions?.authenticated) {
|
||||
if (formData.return) {
|
||||
response.writeHead(303, {"Location": formData.return});
|
||||
} else {
|
||||
response.writeHead(303, {"Location": (request.client.tls ? 'https://' : 'http://') + request.headers.host + '/', "Content-Length": "0"});
|
||||
}
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
let sessionIsNew = false;
|
||||
let loginError;
|
||||
|
||||
let formData = form.decodeForm(request.query);
|
||||
|
||||
if (request.method == "POST" || formData.submit) {
|
||||
sessionIsNew = true;
|
||||
formData = form.decodeForm(utf8Decode(request.body), formData);
|
||||
if (formData.submit == "Login") {
|
||||
let account = gDatabase.get("user:" + formData.name);
|
||||
account = account ? JSON.parse(account) : account;
|
||||
if (formData.register == "1") {
|
||||
if (formData.register == '1') {
|
||||
if (!account &&
|
||||
isNameValid(formData.name) &&
|
||||
formData.password == formData.confirm) {
|
||||
let users = new Set();
|
||||
let users_original = gDatabase.get('users');
|
||||
@ -145,12 +160,23 @@ function handler(request, response) {
|
||||
}
|
||||
session = makeJwt({name: formData.name});
|
||||
account = {password: hashPassword(formData.password)};
|
||||
gDatabase.set("user:" + formData.name, JSON.stringify(account));
|
||||
gDatabase.set('user:' + formData.name, JSON.stringify(account));
|
||||
if (noAdministrator()) {
|
||||
makeAdministrator(formData.name);
|
||||
}
|
||||
} else {
|
||||
loginError = "Error registering account.";
|
||||
loginError = 'Error registering account.';
|
||||
}
|
||||
} else if (formData.change == '1') {
|
||||
if (account &&
|
||||
isNameValid(formData.name) &&
|
||||
formData.new_password == formData.confirm &&
|
||||
verifyPassword(formData.password, account.password)) {
|
||||
session = makeJwt({name: formData.name});
|
||||
account = {password: hashPassword(formData.new_password)};
|
||||
gDatabase.set('user:' + formData.name, JSON.stringify(account));
|
||||
} else {
|
||||
loginError = 'Error changing password.';
|
||||
}
|
||||
} else {
|
||||
if (account &&
|
||||
@ -161,7 +187,7 @@ function handler(request, response) {
|
||||
makeAdministrator(formData.name);
|
||||
}
|
||||
} else {
|
||||
loginError = "Invalid username or password.";
|
||||
loginError = 'Invalid username or password.';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -178,46 +204,16 @@ function handler(request, response) {
|
||||
} else {
|
||||
File.readFile("core/auth.html").then(function(data) {
|
||||
let html = utf8Decode(data);
|
||||
let contents = "";
|
||||
|
||||
if (entry) {
|
||||
if (sessionIsNew) {
|
||||
contents += '<div>Welcome back, ' + entry.name + '.</div>\n';
|
||||
} else {
|
||||
contents += '<div>You are already logged in, ' + entry.name + '.</div>\n';
|
||||
}
|
||||
contents += '<div><a href="/login/logout">Logout</a></div>\n';
|
||||
} else {
|
||||
contents += '<form method="POST">\n';
|
||||
if (loginError) {
|
||||
contents += "<p>" + loginError + "</p>\n";
|
||||
}
|
||||
contents += '<div id="auth_greeting"><b>Halt. Who goes there?</b></div>\n'
|
||||
contents += '<div id="auth">\n';
|
||||
contents += '<div id="auth_login">\n'
|
||||
if (noAdministrator()) {
|
||||
contents += '<div class="notice">There is currently no administrator. You will be made administrator.</div>\n';
|
||||
}
|
||||
contents += '<div><label for="name">Name:</label> <input type="text" id="name" name="name" value=""></div>\n';
|
||||
contents += '<div><label for="password">Password:</label> <input type="password" id="password" name="password" value=""></div>\n';
|
||||
contents += '<div id="confirmPassword" style="display: none"><label for="confirm">Confirm:</label> <input type="password" id="confirm" name="confirm" value=""></div>\n';
|
||||
contents += '<div><input type="checkbox" id="register" name="register" value="1" onchange="showHideConfirm()"> <label for="register">Register a new account</label></div>\n';
|
||||
contents += '<div><input id="loginButton" type="submit" name="submit" value="Login"></div>\n';
|
||||
contents += '</div>';
|
||||
contents += '<div class="auth_or"> - or - </div>';
|
||||
contents += '<div id="auth_guest">\n';
|
||||
contents += '<input id="guestButton" type="submit" name="submit" value="Proceeed as Guest">\n';
|
||||
contents += '</div>\n';
|
||||
contents += '</div>\n';
|
||||
contents += '<div style="text-align: center">\n';
|
||||
contents += '<h2>Code of Conduct</h2>\n';
|
||||
contents += `<div><textarea readonly rows=20 cols=80>${core.globalSettings.code_of_conduct}</textarea></div>\n`;
|
||||
contents += '</div>\n';
|
||||
contents += '</form>';
|
||||
}
|
||||
let text = html.replace("<!--SESSION-->", contents);
|
||||
response.writeHead(200, {"Content-Type": "text/html; charset=utf-8", "Set-Cookie": cookie, "Content-Length": text.length});
|
||||
response.end(text);
|
||||
let auth_data = {
|
||||
session_is_new: sessionIsNew,
|
||||
name: entry?.name,
|
||||
error: loginError,
|
||||
code_of_conduct: core.globalSettings.code_of_conduct,
|
||||
have_administrator: !noAdministrator(),
|
||||
};
|
||||
html = utf8Encode(html.replace('$AUTH_DATA', JSON.stringify(auth_data)));
|
||||
response.writeHead(200, {"Content-Type": "text/html; charset=utf-8", "Set-Cookie": cookie, "Content-Length": html.length});
|
||||
response.end(html);
|
||||
}).catch(function(error) {
|
||||
response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"});
|
||||
response.end("404 File not found");
|
||||
@ -260,12 +256,17 @@ function query(headers) {
|
||||
return {
|
||||
session: entry,
|
||||
permissions: autologin ? getPermissionsForUser(autologin) : getPermissions(session),
|
||||
refresh: {
|
||||
token: makeJwt({name: entry.name}),
|
||||
interval: kRefreshInterval,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { handler, query };
|
||||
function make_refresh(credentials) {
|
||||
if (credentials?.session?.name) {
|
||||
return {
|
||||
token: makeJwt({name: credentials.session.name}),
|
||||
interval: kRefreshInterval,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { handler, query, make_refresh };
|
||||
|
190
core/client.js
190
core/client.js
@ -6,9 +6,6 @@ let gCurrentFile;
|
||||
let gFiles = {};
|
||||
let gApp = {files: {}, emoji: '📦'};
|
||||
let gEditor;
|
||||
let gSplit;
|
||||
let gGraphs = {};
|
||||
let gTimeSeries = {};
|
||||
let gOriginalInput;
|
||||
|
||||
let kErrorColor = "#dc322f";
|
||||
@ -52,6 +49,8 @@ class TfNavigationElement extends LitElement {
|
||||
show_permissions: {type: Boolean},
|
||||
status: {type: Object},
|
||||
spark_lines: {type: Object},
|
||||
version: {type: Object},
|
||||
show_version: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@ -79,6 +78,9 @@ class TfNavigationElement extends LitElement {
|
||||
get_spark_line(key, options) {
|
||||
if (!this.spark_lines[key]) {
|
||||
let spark_line = document.createElement('tf-sparkline');
|
||||
spark_line.style.display = 'flex';
|
||||
spark_line.style.flexDirection = 'row';
|
||||
spark_line.style.flex = '0 50 5em';
|
||||
spark_line.title = key;
|
||||
if (options) {
|
||||
if (options.max) {
|
||||
@ -93,9 +95,9 @@ class TfNavigationElement extends LitElement {
|
||||
|
||||
render_login() {
|
||||
if (this?.credentials?.session?.name) {
|
||||
return html`<a href="/login/logout?return=${url() + hash()}">logout ${this.credentials.session.name}</a>`;
|
||||
return html`<a id="login" href="/login/logout?return=${url() + hash()}">logout ${this.credentials.session.name}</a>`;
|
||||
} else {
|
||||
return html`<a href="/login?return=${url() + hash()}">login</a>`;
|
||||
return html`<a id="login" href="/login?return=${url() + hash()}">login</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,8 +126,9 @@ class TfNavigationElement extends LitElement {
|
||||
<style>
|
||||
${k_global_style}
|
||||
</style>
|
||||
<div style="margin: 4px; display: flex; flex-direction: row; flex-wrap: nowrap; gap: 3px">
|
||||
<span>😎</span>
|
||||
<div style="margin: 4px; display: flex; flex-direction: row; flex-wrap: nowrap; gap: 3px; align-items: center">
|
||||
<span style="cursor: pointer" @click=${() => this.show_version = !this.show_version}>😎</span>
|
||||
<span ?hidden=${!this.show_version} style="flex: 0 0; white-space: nowrap" title=${this.version?.name + ' ' + Object.entries(this.version || {}).filter(x => ['name', 'number'].indexOf(x[0]) == -1).map(x => `\n* ${x[0]}: ${x[1]}`)}>${this.version?.number}</span>
|
||||
<a accesskey="h" data-tip="Open home app." href="/" style="color: #fff; white-space: nowrap">TF</a>
|
||||
<a accesskey="a" data-tip="Open apps list." href="/~core/apps/">apps</a>
|
||||
<a accesskey="e" data-tip="Toggle the app editor." href="#" @click=${this.toggle_edit}>edit</a>
|
||||
@ -133,7 +136,7 @@ class TfNavigationElement extends LitElement {
|
||||
<span style="display: inline-block; vertical-align: top; white-space: pre; color: ${this.status.color ?? kErrorColor}">${this.status.message}</span>
|
||||
<span id="requests"></span>
|
||||
${this.render_permissions()}
|
||||
<span style="flex: 1; white-space: nowrap; overflow: hidden; margin: 0; padding: 0">${Object.keys(this.spark_lines).sort().map(x => this.spark_lines[x]).map(x => [x.dataset.emoji, x])}</span>
|
||||
<span style="flex: 1 1; display: flex; flex-direction: row; white-space: nowrap; margin: 0; padding: 0">${Object.keys(this.spark_lines).sort().map(x => this.spark_lines[x]).map(x => [html`<span style="font-size: xx-small">${x.dataset.emoji}</span>`, x])}</span>
|
||||
<span style="flex: 0 0; white-space: nowrap">${this.render_login()}</span>
|
||||
</div>
|
||||
`;
|
||||
@ -307,7 +310,7 @@ class TfSparkLineElement extends LitElement {
|
||||
render_line(line) {
|
||||
if (line?.values?.length >= 2) {
|
||||
let max = Math.max(this.max, ...line.values);
|
||||
let points = [].concat(...line.values.map((x, i) => [100.0 * i / (line.values.length - 1), 10.0 - 10.0 * (x - this.min) / (max - this.min)]));
|
||||
let points = [].concat(...line.values.map((x, i) => [50.0 * i / (line.values.length - 1), 10.0 - 10.0 * (x - this.min) / (max - this.min)]));
|
||||
return svg`<polyline points=${points.join(' ')} stroke=${line.style} fill="none"/>`;
|
||||
}
|
||||
}
|
||||
@ -315,7 +318,7 @@ class TfSparkLineElement extends LitElement {
|
||||
render() {
|
||||
let max = Math.round(10.0 * Math.max(...this.lines.map(line => line.values[line.values.length - 1]))) / 10.0;
|
||||
return html`
|
||||
<svg style="width: 10em; height: 1.4em; vertical-align: top; margin: 0; padding: 0; background: #000" viewBox="0 0 100 10" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg style="max-width: 7.5em; max-height: 1.5em; margin: 0; padding: 0; background: #000" viewBox="0 0 50 10" xmlns="http://www.w3.org/2000/svg">
|
||||
${this.lines.map(x => this.render_line(x))}
|
||||
<text x="0" y="1em" style="font: 8px sans-serif; fill: #fff">${max}</text>
|
||||
</svg>
|
||||
@ -379,18 +382,18 @@ function editing() {
|
||||
return document.getElementById("editPane").style.display != 'none';
|
||||
}
|
||||
|
||||
function is_edit_only() {
|
||||
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
|
||||
}
|
||||
|
||||
function edit() {
|
||||
if (editing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.localStorage.setItem('editing', '1');
|
||||
if (gSplit) {
|
||||
gSplit.destroy();
|
||||
gSplit = undefined;
|
||||
}
|
||||
gSplit = Split(['#editPane', '#viewPane'], {minSize: 0});
|
||||
document.getElementById("editPane").style.display = 'flex';
|
||||
document.getElementById('viewPane').style.display = is_edit_only() ? 'none' : 'flex';
|
||||
|
||||
ensureLoaded([
|
||||
{tagName: "script", attributes: {src: "/codemirror/codemirror.min.js"}},
|
||||
@ -425,24 +428,6 @@ function trace() {
|
||||
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
|
||||
}
|
||||
|
||||
function stats() {
|
||||
window.localStorage.setItem('stats', '1');
|
||||
document.getElementById("statsPane").style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeStats() {
|
||||
window.localStorage.setItem('stats', '0');
|
||||
document.getElementById("statsPane").style.display = 'none';
|
||||
}
|
||||
|
||||
function toggleStats() {
|
||||
if (document.getElementById("statsPane").style.display == 'none') {
|
||||
stats();
|
||||
} else {
|
||||
closeStats();
|
||||
}
|
||||
}
|
||||
|
||||
function guessMode(name) {
|
||||
return name.endsWith(".js") ? "javascript" :
|
||||
name.endsWith(".html") ? "htmlmixed" :
|
||||
@ -527,10 +512,7 @@ function load(path) {
|
||||
function closeEditor() {
|
||||
window.localStorage.setItem('editing', '0');
|
||||
document.getElementById("editPane").style.display = 'none';
|
||||
if (gSplit) {
|
||||
gSplit.destroy();
|
||||
gSplit = undefined;
|
||||
}
|
||||
document.getElementById('viewPane').style.display = 'flex';
|
||||
}
|
||||
|
||||
function explodePath() {
|
||||
@ -718,11 +700,13 @@ function api_requestPermission(permission, id) {
|
||||
|
||||
const k_options = [
|
||||
{
|
||||
id: 'allow',
|
||||
text: '✅ Allow',
|
||||
grant: ['allow once', 'allow'],
|
||||
|
||||
},
|
||||
{
|
||||
id: 'deny',
|
||||
text: '❌ Deny',
|
||||
grant: ['deny once', 'deny'],
|
||||
},
|
||||
@ -733,6 +717,7 @@ function api_requestPermission(permission, id) {
|
||||
for (let option of k_options) {
|
||||
let button = document.createElement('button');
|
||||
button.innerText = option.text;
|
||||
button.id = option.id;
|
||||
button.onclick = function() {
|
||||
resolve(option.grant[check.checked ? 1 : 0]);
|
||||
document.body.removeChild(outer);
|
||||
@ -765,6 +750,8 @@ function _receive_websocket_message(message) {
|
||||
if (window.location.hash) {
|
||||
send({event: "hashChange", hash: window.location.hash});
|
||||
}
|
||||
document.getElementsByTagName('tf-navigation')[0].version = message.version;
|
||||
document.getElementById('viewPane').style.display = message.edit_only ? 'none' : 'flex';
|
||||
send({action: 'enableStats', enabled: true});
|
||||
} else if (message && message.action == "ping") {
|
||||
send({action: "pong"});
|
||||
@ -797,63 +784,13 @@ function _receive_websocket_message(message) {
|
||||
};
|
||||
const k_colors = ['#0f0', '#88f', '#ff0', '#f0f', '#0ff', '#f00', '#888'];
|
||||
let graph_key = k_groups[key]?.group || key;
|
||||
let graph = gGraphs[graph_key];
|
||||
if (!graph) {
|
||||
graph = {
|
||||
chart: new SmoothieChart({
|
||||
millisPerPixel: 100,
|
||||
minValue: 0,
|
||||
grid: {
|
||||
millisPerLine: 1000,
|
||||
verticalSections: 10,
|
||||
},
|
||||
tooltip: true,
|
||||
}),
|
||||
canvas: document.createElement('canvas'),
|
||||
title: document.createElement('div'),
|
||||
series: [],
|
||||
};
|
||||
gGraphs[graph_key] = graph;
|
||||
graph.canvas.width = 240;
|
||||
graph.canvas.height = 64;
|
||||
graph.title.innerText = graph_key;
|
||||
graph.title.style.flex = '0';
|
||||
document.getElementById('graphs').appendChild(graph.title);
|
||||
document.getElementById('graphs').appendChild(graph.canvas);
|
||||
graph.chart.streamTo(graph.canvas, 1000);
|
||||
}
|
||||
|
||||
let timeseries = gTimeSeries[key];
|
||||
if (!timeseries) {
|
||||
let is_multi = key != graph_key || graph.series.length > 1;
|
||||
timeseries = new TimeSeries();
|
||||
gTimeSeries[key] = timeseries;
|
||||
graph.chart.addTimeSeries(timeseries, {lineWidth: 2, strokeStyle: is_multi ? k_colors[graph.series.length] : '#fff'});
|
||||
graph.series.push(k_groups[key]?.name || key);
|
||||
if (is_multi) {
|
||||
while (graph.title.firstChild) {
|
||||
graph.title.removeChild(graph.title.firstChild);
|
||||
}
|
||||
function makeColoredText(text, color) {
|
||||
let element = document.createElement('span');
|
||||
element.style.color = color;
|
||||
element.innerText = text;
|
||||
return element;
|
||||
}
|
||||
graph.title.appendChild(makeColoredText(graph_key + ':', '#fff'));
|
||||
for (let series of graph.series) {
|
||||
graph.title.appendChild(makeColoredText(' ' + series, k_colors[graph.series.indexOf(series)]));
|
||||
}
|
||||
}
|
||||
}
|
||||
timeseries.append(now, message.stats[key]);
|
||||
|
||||
if (graph_key == 'cpu' || graph_key == 'rpc' || graph_key == 'store') {
|
||||
if (['cpu', 'rpc', 'store', 'memory'].indexOf(graph_key) != -1) {
|
||||
let line = document.getElementsByTagName('tf-navigation')[0].get_spark_line(graph_key, { max: 100 });
|
||||
line.dataset.emoji = {
|
||||
'cpu': '💻',
|
||||
'rpc': '🔁',
|
||||
'store': '💾',
|
||||
'memory': '🐏',
|
||||
}[graph_key];
|
||||
line.append(key, message.stats[key]);
|
||||
}
|
||||
@ -896,22 +833,6 @@ function send(value) {
|
||||
}
|
||||
}
|
||||
|
||||
function dragHover(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
let input = document.getElementById("input");
|
||||
if (event.type == "dragover") {
|
||||
if (!input.classList.contains("drop")) {
|
||||
input.classList.add("drop");
|
||||
gOriginalInput = input.value;
|
||||
input.value = "drop file to upload";
|
||||
}
|
||||
} else {
|
||||
input.classList.remove("drop");
|
||||
input.value = gOriginalInput;
|
||||
}
|
||||
}
|
||||
|
||||
function fixImage(sourceData, maxWidth, maxHeight, callback) {
|
||||
let result = sourceData;
|
||||
let image = new Image();
|
||||
@ -941,52 +862,6 @@ function sendImage(image) {
|
||||
});
|
||||
}
|
||||
|
||||
function fileDropRead(event) {
|
||||
sendImage(event.target.result);
|
||||
}
|
||||
|
||||
function fileDrop(event) {
|
||||
dragHover(event);
|
||||
|
||||
let done = false;
|
||||
if (!done) {
|
||||
let files = event.target.files || event.dataTransfer.files;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let file = files[i];
|
||||
if (file.type.substring(0, "image/".length) == "image/") {
|
||||
let reader = new FileReader();
|
||||
reader.onloadend = fileDropRead;
|
||||
reader.readAsDataURL(file);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
let html = event.dataTransfer.getData("text/html");
|
||||
let match = /<img.*src="([^"]+)"/.exec(html);
|
||||
if (match) {
|
||||
sendImage(match[1]);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
let text = event.dataTransfer.getData("text/plain");
|
||||
if (text) {
|
||||
send(text);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function enableDragDrop() {
|
||||
let body = document.body;
|
||||
body.addEventListener("dragover", dragHover);
|
||||
body.addEventListener("dragleave", dragHover);
|
||||
body.addEventListener("drop", fileDrop);
|
||||
}
|
||||
|
||||
function hashChange() {
|
||||
send({event: 'hashChange', hash: window.location.hash});
|
||||
}
|
||||
@ -1065,6 +940,8 @@ function connectSocket(path) {
|
||||
gSocket.send(JSON.stringify({
|
||||
action: "hello",
|
||||
path: connect_path,
|
||||
url: window.location.href,
|
||||
edit_only: editing() && is_edit_only(),
|
||||
api: Object.entries(k_api).map(([key, value]) => [].concat([key], value.args)),
|
||||
}));
|
||||
}
|
||||
@ -1146,7 +1023,6 @@ window.addEventListener("load", function() {
|
||||
window.addEventListener("message", message, false);
|
||||
window.addEventListener("online", connectSocket);
|
||||
document.getElementById("name").value = window.location.pathname;
|
||||
document.getElementById('closeStats').addEventListener('click', () => closeStats());
|
||||
document.getElementById('closeEditor').addEventListener('click', () => closeEditor());
|
||||
document.getElementById('save').addEventListener('click', () => save());
|
||||
document.getElementById('icon').addEventListener('click', () => changeIcon());
|
||||
@ -1155,10 +1031,6 @@ window.addEventListener("load", function() {
|
||||
event.preventDefault();
|
||||
trace();
|
||||
});
|
||||
document.getElementById('stats_button').addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
toggleStats();
|
||||
});
|
||||
for (let tag of document.getElementsByTagName('a')) {
|
||||
if (tag.accessKey) {
|
||||
tag.classList.add('tooltip_parent');
|
||||
@ -1183,7 +1055,6 @@ window.addEventListener("load", function() {
|
||||
tag.appendChild(tooltip);
|
||||
}
|
||||
}
|
||||
enableDragDrop();
|
||||
connectSocket(window.location.pathname);
|
||||
|
||||
if (window.localStorage.getItem('editing') == '1') {
|
||||
@ -1191,9 +1062,4 @@ window.addEventListener("load", function() {
|
||||
} else {
|
||||
closeEditor();
|
||||
}
|
||||
if (window.localStorage.getItem('stats') == '1') {
|
||||
stats();
|
||||
} else {
|
||||
closeStats();
|
||||
}
|
||||
});
|
||||
|
53
core/core.js
53
core/core.js
@ -1,6 +1,7 @@
|
||||
import * as app from './app.js';
|
||||
import * as auth from './auth.js';
|
||||
import * as form from './form.js';
|
||||
import * as http from './http.js';
|
||||
import * as httpd from './httpd.js';
|
||||
|
||||
let gProcessIndex = 0;
|
||||
@ -66,6 +67,21 @@ const k_global_settings = {
|
||||
default_value: undefined,
|
||||
description: 'If connecting by HTTP and HTTPS is configured, Location header prefix (ie, "https://example.com")',
|
||||
},
|
||||
fetch_hosts: {
|
||||
type: 'string',
|
||||
default_value: undefined,
|
||||
description: 'Comma-separated list of host names to which HTTP fetch requests are allowed. None if empty.',
|
||||
},
|
||||
blob_fetch_age_seconds: {
|
||||
type: 'integer',
|
||||
default_value: (platform() == 'android' ? 0.5 * 365 * 24 * 60 * 60 : undefined),
|
||||
description: 'Only blobs mentioned more recently than this age will be automatically fetched.',
|
||||
},
|
||||
blob_expire_age_seconds: {
|
||||
type: 'integer',
|
||||
default_value: (platform() == 'android' ? 1.0 * 365 * 24 * 60 * 60 : undefined),
|
||||
description: 'Blobs older than this will be automatically deleted.',
|
||||
},
|
||||
};
|
||||
|
||||
let gGlobalSettings = {
|
||||
@ -299,6 +315,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
throw Error(`Permission denied: ${permission}.`);
|
||||
}
|
||||
},
|
||||
url: options?.url,
|
||||
}
|
||||
};
|
||||
if (process.credentials?.permissions?.administration) {
|
||||
@ -321,7 +338,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
print('Done.');
|
||||
};
|
||||
imports.core.deleteUser = function(user) {
|
||||
return imports.core.permissionTest('delete_user').then(function() {
|
||||
return Promise.resolve(imports.core.permissionTest('delete_user')).then(function() {
|
||||
let db = new Database('auth');
|
||||
|
||||
db.remove('user:' + user);
|
||||
@ -385,7 +402,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
if (process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name) {
|
||||
return imports.core.permissionTest('ssb_append').then(function() {
|
||||
return Promise.resolve(imports.core.permissionTest('ssb_append')).then(function() {
|
||||
return ssb.appendMessageWithIdentity(process.credentials.session.name, id, message);
|
||||
});
|
||||
}
|
||||
@ -404,6 +421,9 @@ async function getProcessBlob(blobId, key, options) {
|
||||
return ssb.privateMessageDecrypt(process.credentials.session.name, id, message);
|
||||
}
|
||||
};
|
||||
imports.fetch = function(url, options) {
|
||||
return http.fetch(url, options, gGlobalSettings.fetch_hosts);
|
||||
}
|
||||
|
||||
if (process.credentials &&
|
||||
process.credentials.session &&
|
||||
@ -456,7 +476,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
}
|
||||
broadcastEvent('onSessionBegin', [getUser(process, process)]);
|
||||
if (process.app) {
|
||||
process.app.send({action: "ready"});
|
||||
process.app.send({action: "ready", version: version()});
|
||||
process.sendPermissions();
|
||||
}
|
||||
await process.task.execute({name: appSourceName, source: appSource});
|
||||
@ -583,12 +603,12 @@ function guessTypeFromMagicBytes(data) {
|
||||
}
|
||||
}
|
||||
|
||||
function sendData(response, data, type, headers) {
|
||||
function sendData(response, data, type, headers, status_code) {
|
||||
if (data) {
|
||||
response.writeHead(200, Object.assign({"Content-Type": type || guessTypeFromMagicBytes(data) || "application/binary", "Content-Length": data.byteLength}, headers || {}));
|
||||
response.writeHead(status_code ?? 200, Object.assign({"Content-Type": type || guessTypeFromMagicBytes(data) || "application/binary", "Content-Length": data.byteLength}, headers || {}));
|
||||
response.end(data);
|
||||
} else {
|
||||
response.writeHead(404, Object.assign({"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length}, headers || {}));
|
||||
response.writeHead(status_code ?? 404, Object.assign({"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length}, headers || {}));
|
||||
response.end("File not found");
|
||||
}
|
||||
}
|
||||
@ -604,7 +624,8 @@ async function getBlobOrContent(id) {
|
||||
}
|
||||
|
||||
let g_handler_index = 0;
|
||||
async function useAppHandler(response, handler_blob_id, path) {
|
||||
async function useAppHandler(response, handler_blob_id, path, query, headers, packageOwner, packageName) {
|
||||
print('useAppHandler', packageOwner, packageName);
|
||||
let do_resolve;
|
||||
let promise = new Promise(async function(resolve, reject) {
|
||||
do_resolve = resolve;
|
||||
@ -617,9 +638,13 @@ async function useAppHandler(response, handler_blob_id, path) {
|
||||
imports: {
|
||||
request: {
|
||||
path: path,
|
||||
query: query,
|
||||
},
|
||||
respond: do_resolve,
|
||||
},
|
||||
credentials: auth.query(headers),
|
||||
packageOwner: packageOwner,
|
||||
packageName: packageName,
|
||||
});
|
||||
await process.ready;
|
||||
|
||||
@ -782,7 +807,11 @@ async function blobHandler(request, response, blobId, uri) {
|
||||
let match;
|
||||
let id;
|
||||
let app_id = blobId;
|
||||
let packageOwner;
|
||||
let packageName;
|
||||
if (match = /^\/\~(\w+)\/(\w+)$/.exec(blobId)) {
|
||||
packageOwner = match[1];
|
||||
packageName = match[2];
|
||||
let db = new Database(match[1]);
|
||||
app_id = await db.get('path:' + match[2]);
|
||||
}
|
||||
@ -792,7 +821,7 @@ async function blobHandler(request, response, blobId, uri) {
|
||||
if (!id && app_object.files['handler.js']) {
|
||||
let answer;
|
||||
try {
|
||||
answer = await useAppHandler(response, app_id, uri.substring(1));
|
||||
answer = await useAppHandler(response, app_id, uri.substring(1), request.query ? form.decodeForm(request.query) : undefined, request.headers, packageOwner, packageName);
|
||||
} catch (error) {
|
||||
data = utf8Encode(`Internal Server Error\n\n${error?.message}\n${error?.stack}`);
|
||||
response.writeHead(500, {'Content-Type': 'text/plain; charset=utf-8', 'Content-Length': data.length});
|
||||
@ -802,10 +831,10 @@ async function blobHandler(request, response, blobId, uri) {
|
||||
if (answer && typeof answer.data == 'string') {
|
||||
answer.data = utf8Encode(answer.data);
|
||||
}
|
||||
sendData(response, answer?.data, answer?.content_type, {
|
||||
sendData(response, answer?.data, answer?.content_type, Object.assign(answer?.headers ?? {}, {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Security-Policy': 'sandbox',
|
||||
});
|
||||
}), answer.status_code);
|
||||
} else if (id) {
|
||||
if (request.headers['if-none-match'] && request.headers['if-none-match'] == '"' + id + '"') {
|
||||
let headers = {
|
||||
@ -905,10 +934,6 @@ loadSettings().then(function() {
|
||||
return staticDirectoryHandler(request, response, 'deps/codemirror/', match[1]);
|
||||
} else if (match = /^\/speedscope\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||
return staticDirectoryHandler(request, response, 'deps/speedscope/', match[1]);
|
||||
} else if (match = /^\/split\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||
return staticDirectoryHandler(request, response, 'deps/split/', match[1]);
|
||||
} else if (match = /^\/smoothie\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||
return staticDirectoryHandler(request, response, 'deps/smoothie/', match[1]);
|
||||
} else if (match = /^\/static(\/.*)/.exec(request.uri)) {
|
||||
return staticFileHandler(request, response, null, match[1]);
|
||||
} else if (request.uri == "/robots.txt") {
|
||||
|
86
core/http.js
Normal file
86
core/http.js
Normal file
@ -0,0 +1,86 @@
|
||||
function parseUrl(url) {
|
||||
// XXX: Hack.
|
||||
let match = url.match(new RegExp("(\\w+)://([^/:]+)(?::(\\d+))?(.*)"));
|
||||
return {
|
||||
protocol: match[1],
|
||||
host: match[2],
|
||||
path: match[4],
|
||||
port: match[3] ? parseInt(match[3]) : match[1] == "http" ? 80 : 443,
|
||||
};
|
||||
}
|
||||
|
||||
function parseResponse(data) {
|
||||
let firstLine;
|
||||
let headers = {};
|
||||
while (true) {
|
||||
let endLine = data.indexOf('\r\n');
|
||||
let line = data.substring(0, endLine);
|
||||
data = data.substring(endLine + 2);
|
||||
if (!line.length) {
|
||||
break;
|
||||
} else if (!firstLine) {
|
||||
firstLine = line;
|
||||
} else {
|
||||
let colon = line.indexOf(":");
|
||||
headers[line.substring(colon)] = line.substring(colon + 1);
|
||||
}
|
||||
}
|
||||
return {body: data};
|
||||
}
|
||||
|
||||
export function fetch(url, options, allowed_hosts) {
|
||||
let parsed = parseUrl(url);
|
||||
return new Promise(function(resolve, reject) {
|
||||
if ((allowed_hosts ?? []).indexOf(parsed.host) == -1) {
|
||||
throw new Error(`fetch() request to host ${parsed.host} is not allowed.`);
|
||||
}
|
||||
let socket = new Socket();
|
||||
let buffer = new Uint8Array(0);
|
||||
|
||||
return socket.connect(parsed.host, parsed.port).then(function() {
|
||||
socket.read(function(data) {
|
||||
if (data && data.length) {
|
||||
let newBuffer = new Uint8Array(buffer.length + data.length);
|
||||
newBuffer.set(buffer, 0);
|
||||
newBuffer.set(data, buffer.length);
|
||||
buffer = newBuffer;
|
||||
} else {
|
||||
let result = parseHttpResponse(buffer);
|
||||
if (!result) {
|
||||
reject(new Exception('Parse failed.'));
|
||||
}
|
||||
if (typeof result == 'number') {
|
||||
if (result == -2) {
|
||||
reject('Incomplete request.');
|
||||
} else {
|
||||
reject('Bad request.');
|
||||
}
|
||||
} else if (typeof result == 'object') {
|
||||
resolve({
|
||||
body: buffer.slice(result.bytes_parsed),
|
||||
status: result.status,
|
||||
message: result.message,
|
||||
headers: result.headers,
|
||||
});
|
||||
} else {
|
||||
reject(new Exception('Unexpected parse result.'));
|
||||
}
|
||||
resolve(parseResponse(utf8Decode(buffer)));
|
||||
}
|
||||
});
|
||||
|
||||
if (parsed.port == 443) {
|
||||
return socket.startTls();
|
||||
}
|
||||
}).then(function() {
|
||||
let body = typeof options?.body == 'string' ? utf8Encode(options.body) : (options.body || new Uint8Array(0));
|
||||
let headers = utf8Encode(`${options?.method ?? 'GET'} ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\nContent-Length: ${body.length}\r\n\r\n`);
|
||||
let fullRequest = new Uint8Array(headers.length + body.length);
|
||||
fullRequest.set(headers, 0);
|
||||
fullRequest.set(body, headers.length);
|
||||
socket.write(fullRequest);
|
||||
}).catch(function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
@ -4,7 +4,7 @@ let gHandlers = [];
|
||||
let gSocketHandlers = [];
|
||||
let gBadRequests = {};
|
||||
|
||||
const kRequestTimeout = 15000;
|
||||
const kRequestTimeout = 5000;
|
||||
const kStallTimeout = 60000;
|
||||
|
||||
function logError(error) {
|
||||
@ -395,41 +395,10 @@ function handleConnection(client) {
|
||||
let parsing_header = true;
|
||||
let bodyToRead = -1;
|
||||
let body;
|
||||
let requestCount = -1;
|
||||
let readCount = 0;
|
||||
let isWebsocket = false;
|
||||
|
||||
function resetTimeout(requestIndex) {
|
||||
if (isWebsocket) {
|
||||
return;
|
||||
}
|
||||
if (bodyToRead == -1) {
|
||||
setTimeout(function() {
|
||||
if (requestCount == requestIndex) {
|
||||
client.info = 'timed out';
|
||||
if (requestCount == 0) {
|
||||
badRequest(client, 'Timed out waiting for request.');
|
||||
} else {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}, kRequestTimeout);
|
||||
} else {
|
||||
let lastReadCount = readCount;
|
||||
setTimeout(function() {
|
||||
if (readCount == lastReadCount) {
|
||||
client.info = 'stalled';
|
||||
if (requestCount == 0) {
|
||||
badRequest(client, 'Request stalled.');
|
||||
} else {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}, kStallTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
resetTimeout(++requestCount);
|
||||
client.setActivityTimeout(kRequestTimeout);
|
||||
|
||||
function reset() {
|
||||
request = undefined;
|
||||
@ -438,7 +407,7 @@ function handleConnection(client) {
|
||||
bodyToRead = -1;
|
||||
body = undefined;
|
||||
client.info = 'reset';
|
||||
resetTimeout(++requestCount);
|
||||
client.setActivityTimeout(kRequestTimeout);
|
||||
}
|
||||
|
||||
function finish() {
|
||||
@ -463,9 +432,6 @@ function handleConnection(client) {
|
||||
client.read(function(data) {
|
||||
readCount++;
|
||||
if (data) {
|
||||
if (bodyToRead != -1 && !isWebsocket) {
|
||||
resetTimeout(requestCount);
|
||||
}
|
||||
let newBuffer = new Uint8Array(inputBuffer.length + data.length);
|
||||
newBuffer.set(inputBuffer, 0);
|
||||
newBuffer.set(data, inputBuffer.length);
|
||||
@ -473,7 +439,7 @@ function handleConnection(client) {
|
||||
|
||||
if (parsing_header)
|
||||
{
|
||||
let result = parseHttp(inputBuffer, inputBuffer.length - data.length);
|
||||
let result = parseHttpRequest(inputBuffer, inputBuffer.length - data.length);
|
||||
if (result) {
|
||||
if (typeof result === 'number') {
|
||||
if (result == -2) {
|
||||
@ -483,6 +449,7 @@ function handleConnection(client) {
|
||||
return;
|
||||
}
|
||||
} else if (typeof result === 'object') {
|
||||
client.setActivityTimeout(kStallTimeout);
|
||||
request = [
|
||||
result.method,
|
||||
result.path,
|
||||
@ -509,7 +476,6 @@ function handleConnection(client) {
|
||||
}
|
||||
body = new Uint8Array(bodyToRead);
|
||||
client.info = 'waiting for body';
|
||||
resetTimeout(requestCount);
|
||||
} else if (headers["connection"]
|
||||
&& headers["connection"].toLowerCase().split(",").map(x => x.trim()).indexOf("upgrade") != -1
|
||||
&& headers["upgrade"]
|
||||
@ -520,7 +486,7 @@ function handleConnection(client) {
|
||||
let response = new Response(requestObject, client);
|
||||
handleWebSocketRequest(requestObject, response, client);
|
||||
/* Prevent the timeout from disconnecting us. */
|
||||
requestCount++;
|
||||
client.setActivityTimeout();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
@ -552,7 +518,7 @@ function handleConnection(client) {
|
||||
}
|
||||
|
||||
let kBacklog = 8;
|
||||
let kHost = "0.0.0.0"
|
||||
let kHost = '::';
|
||||
|
||||
let socket = new Socket();
|
||||
socket.bind(kHost, tildefriends.http_port).then(function(port) {
|
||||
|
@ -9,13 +9,7 @@
|
||||
<body style="display: flex; flex-flow: column">
|
||||
<tf-navigation></tf-navigation>
|
||||
<div id="content" class="hbox" style="flex: 1 1; width: 100%">
|
||||
<div id="statsPane" class="vbox" style="display: none; flex 1 1">
|
||||
<div class="hbox">
|
||||
<input type="button" id="closeStats" name="closeStats" value="Close">
|
||||
</div>
|
||||
<div id="graphs" class="vbox" style="height: 100%"></div>
|
||||
</div>
|
||||
<div id="editPane" class="vbox" style="display: none">
|
||||
<div id="editPane" class="vbox" style="flex: 1 1; display: none">
|
||||
<div class="navigation hbox">
|
||||
<input type="button" id="closeEditor" name="closeEditor" value="Close">
|
||||
<input type="button" id="save" name="save" value="Save">
|
||||
@ -23,7 +17,6 @@
|
||||
<input type="text" id="name" name="name" style="flex: 1 1; min-width: 1em"></input>
|
||||
<input type="button" id="delete" name="delete" value="Delete">
|
||||
<input type="button" id="trace_button" value="Trace">
|
||||
<input type="button" id="stats_button" value="Stats">
|
||||
</div>
|
||||
<div class="hbox" style="height: 100%">
|
||||
<tf-files-pane></tf-files-pane>
|
||||
@ -34,13 +27,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="viewPane" class="vbox" style="flex: 1 0; overflow: auto">
|
||||
<iframe id="document" sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-downloads" style="width: 100%; height: 100%; border: 0"></iframe>
|
||||
<div id="viewPane" class="vbox" style="flex: 1 1; overflow: auto">
|
||||
<iframe id="document" sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-popups allow-downloads" style="width: 100%; height: 100%; border: 0"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<script>window.litDisableBundleWarning = true;</script>
|
||||
<script src="/split/split.min.js"></script>
|
||||
<script src="/smoothie/smoothie.js"></script>
|
||||
<script src="/static/client.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -64,11 +64,6 @@ a:active {
|
||||
color: #eee8d5;
|
||||
}
|
||||
|
||||
#input.drop {
|
||||
border: 2px solid;
|
||||
color: #cb4b16;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
@ -105,33 +100,6 @@ a:active {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#auth_greeting {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#auth {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#auth_login {
|
||||
flex: 0 1 auto;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.auth_or {
|
||||
flex: 0 1 auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#auth_guest {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.notice {
|
||||
color: #cb4b16;
|
||||
margin: 1em;
|
||||
|
2
deps/codemirror/annotatescrollbar.min.js
vendored
2
deps/codemirror/annotatescrollbar.min.js
vendored
@ -1 +1 @@
|
||||
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],t):t(CodeMirror)}(function(t){"use strict";function e(t,e){function i(t){clearTimeout(n.doRedraw),n.doRedraw=setTimeout(function(){n.redraw()},t)}this.cm=t,this.options=e,this.buttonHeight=e.scrollButtonHeight||t.getOption("scrollButtonHeight"),this.annotations=[],this.doRedraw=this.doUpdate=null,this.div=t.getWrapperElement().appendChild(document.createElement("div")),this.div.style.cssText="position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none",this.computeScale();var n=this;t.on("refresh",this.resizeHandler=function(){clearTimeout(n.doUpdate),n.doUpdate=setTimeout(function(){n.computeScale()&&i(20)},100)}),t.on("markerAdded",this.resizeHandler),t.on("markerCleared",this.resizeHandler),!1!==e.listenForChanges&&t.on("changes",this.changeHandler=function(){i(250)})}t.defineExtension("annotateScrollbar",function(t){return new e(this,t="string"==typeof t?{className:t}:t)}),t.defineOption("scrollButtonHeight",0),e.prototype.computeScale=function(){var t=this.cm,t=(t.getWrapperElement().clientHeight-t.display.barHeight-2*this.buttonHeight)/t.getScrollerElement().scrollHeight;if(t!=this.hScale)return this.hScale=t,!0},e.prototype.update=function(t){this.annotations=t,this.redraw()},e.prototype.redraw=function(t){!1!==t&&this.computeScale();var n=this.cm,e=this.hScale,i=document.createDocumentFragment(),o=this.annotations,r=n.getOption("lineWrapping"),a=r&&1.5*n.defaultTextHeight(),s=null,h=null;function l(t,e){var i;return s!=t.line&&(s=t.line,h=n.getLineHandle(t.line),(i=n.getLineHandleVisualStart(h))!=h&&(s=n.getLineNumber(i),h=i)),h.widgets&&h.widgets.length||r&&h.height>a?n.charCoords(t,"local")[e?"top":"bottom"]:n.heightAtLine(h,"local")+(e?0:h.height)}var d=n.lastLine();if(n.display.barWidth)for(var c,p=0;p<o.length;p++){var u=o[p];if(!(u.to.line>d)){for(var m,f,g=c||l(u.from,!0)*e,H=l(u.to,!1)*e;p<o.length-1&&!(o[p+1].to.line>d)&&!(H+.9<(c=l(o[p+1].from,!0)*e));)H=l((u=o[++p]).to,!1)*e;H!=g&&(m=Math.max(H-g,3),(f=i.appendChild(document.createElement("div"))).style.cssText="position: absolute; right: 0px; width: "+Math.max(n.display.barWidth-1,2)+"px; top: "+(g+this.buttonHeight)+"px; height: "+m+"px",f.className=this.options.className,u.id&&f.setAttribute("annotation-id",u.id))}}this.div.textContent="",this.div.appendChild(i)},e.prototype.clear=function(){this.cm.off("refresh",this.resizeHandler),this.cm.off("markerAdded",this.resizeHandler),this.cm.off("markerCleared",this.resizeHandler),this.changeHandler&&this.cm.off("changes",this.changeHandler),this.div.parentNode.removeChild(this.div)}});
|
||||
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],t):t(CodeMirror)}(function(t){"use strict";function e(t,e){function i(t){clearTimeout(n.doRedraw),n.doRedraw=setTimeout(function(){n.redraw()},t)}this.cm=t,this.options=e,this.buttonHeight=e.scrollButtonHeight||t.getOption("scrollButtonHeight"),this.annotations=[],this.doRedraw=this.doUpdate=null,this.div=t.getWrapperElement().appendChild(document.createElement("div")),this.div.style.cssText="position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none",this.computeScale();var n=this;t.on("refresh",this.resizeHandler=function(){clearTimeout(n.doUpdate),n.doUpdate=setTimeout(function(){n.computeScale()&&i(20)},100)}),t.on("markerAdded",this.resizeHandler),t.on("markerCleared",this.resizeHandler),!1!==e.listenForChanges&&t.on("changes",this.changeHandler=function(){i(250)})}t.defineExtension("annotateScrollbar",function(t){return new e(this,t="string"==typeof t?{className:t}:t)}),t.defineOption("scrollButtonHeight",0),e.prototype.computeScale=function(){var t=this.cm,t=(t.getWrapperElement().clientHeight-t.display.barHeight-2*this.buttonHeight)/t.getScrollerElement().scrollHeight;if(t!=this.hScale)return this.hScale=t,!0},e.prototype.update=function(t){this.annotations=t,this.redraw()},e.prototype.redraw=function(t){!1!==t&&this.computeScale();var n=this.cm,e=this.hScale,i=document.createDocumentFragment(),o=this.annotations,r=n.getOption("lineWrapping"),a=r&&1.5*n.defaultTextHeight(),s=null,h=null;function l(t,e){var i;return s!=t.line&&(s=t.line,h=n.getLineHandle(t.line),(i=n.getLineHandleVisualStart(h))!=h)&&(s=n.getLineNumber(i),h=i),h.widgets&&h.widgets.length||r&&h.height>a?n.charCoords(t,"local")[e?"top":"bottom"]:n.heightAtLine(h,"local")+(e?0:h.height)}var d=n.lastLine();if(n.display.barWidth)for(var c,p=0;p<o.length;p++){var u=o[p];if(!(u.to.line>d)){for(var m,f,g=c||l(u.from,!0)*e,H=l(u.to,!1)*e;p<o.length-1&&!(o[p+1].to.line>d)&&!(H+.9<(c=l(o[p+1].from,!0)*e));)H=l((u=o[++p]).to,!1)*e;H!=g&&(m=Math.max(H-g,3),(f=i.appendChild(document.createElement("div"))).style.cssText="position: absolute; right: 0px; width: "+Math.max(n.display.barWidth-1,2)+"px; top: "+(g+this.buttonHeight)+"px; height: "+m+"px",f.className=this.options.className,u.id)&&f.setAttribute("annotation-id",u.id)}}this.div.textContent="",this.div.appendChild(i)},e.prototype.clear=function(){this.cm.off("refresh",this.resizeHandler),this.cm.off("markerAdded",this.resizeHandler),this.cm.off("markerCleared",this.resizeHandler),this.changeHandler&&this.cm.off("changes",this.changeHandler),this.div.parentNode.removeChild(this.div)}});
|
2
deps/codemirror/codemirror.min.js
vendored
2
deps/codemirror/codemirror.min.js
vendored
File diff suppressed because one or more lines are too long
2
deps/codemirror/css.min.js
vendored
2
deps/codemirror/css.min.js
vendored
File diff suppressed because one or more lines are too long
2
deps/codemirror/dialog.min.js
vendored
2
deps/codemirror/dialog.min.js
vendored
@ -1 +1 @@
|
||||
!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(s){function f(e,o,n){var e=e.getWrapperElement(),t=e.appendChild(document.createElement("div"));return t.className=n?"CodeMirror-dialog CodeMirror-dialog-bottom":"CodeMirror-dialog CodeMirror-dialog-top","string"==typeof o?t.innerHTML=o:t.appendChild(o),s.addClass(e,"dialog-opened"),t}function p(e,o){e.state.currentNotificationClose&&e.state.currentNotificationClose(),e.state.currentNotificationClose=o}s.defineExtension("openDialog",function(e,o,n){n=n||{},p(this,null);var t=f(this,e,n.bottom),i=!1,r=this;function u(e){"string"==typeof e?l.value=e:i||(i=!0,s.rmClass(t.parentNode,"dialog-opened"),t.parentNode.removeChild(t),r.focus(),n.onClose&&n.onClose(t))}var l=t.getElementsByTagName("input")[0];return l?(l.focus(),n.value&&(l.value=n.value,!1!==n.selectValueOnOpen&&l.select()),n.onInput&&s.on(l,"input",function(e){n.onInput(e,l.value,u)}),n.onKeyUp&&s.on(l,"keyup",function(e){n.onKeyUp(e,l.value,u)}),s.on(l,"keydown",function(e){n&&n.onKeyDown&&n.onKeyDown(e,l.value,u)||((27==e.keyCode||!1!==n.closeOnEnter&&13==e.keyCode)&&(l.blur(),s.e_stop(e),u()),13==e.keyCode&&o(l.value,e))}),!1!==n.closeOnBlur&&s.on(t,"focusout",function(e){null!==e.relatedTarget&&u()})):(e=t.getElementsByTagName("button")[0])&&(s.on(e,"click",function(){u(),r.focus()}),!1!==n.closeOnBlur&&s.on(e,"blur",u),e.focus()),u}),s.defineExtension("openConfirm",function(e,o,n){p(this,null);var t=f(this,e,n&&n.bottom),i=t.getElementsByTagName("button"),r=!1,u=this,l=1;function a(){r||(r=!0,s.rmClass(t.parentNode,"dialog-opened"),t.parentNode.removeChild(t),u.focus())}i[0].focus();for(var c=0;c<i.length;++c){var d=i[c];!function(o){s.on(d,"click",function(e){s.e_preventDefault(e),a(),o&&o(u)})}(o[c]),s.on(d,"blur",function(){--l,setTimeout(function(){l<=0&&a()},200)}),s.on(d,"focus",function(){++l})}}),s.defineExtension("openNotification",function(e,o){p(this,r);var n,t=f(this,e,o&&o.bottom),i=!1,e=o&&void 0!==o.duration?o.duration:5e3;function r(){i||(i=!0,clearTimeout(n),s.rmClass(t.parentNode,"dialog-opened"),t.parentNode.removeChild(t))}return s.on(t,"click",function(e){s.e_preventDefault(e),r()}),e&&(n=setTimeout(r,e)),r})});
|
||||
!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(s){function f(e,o,n){var e=e.getWrapperElement(),t=e.appendChild(document.createElement("div"));return t.className=n?"CodeMirror-dialog CodeMirror-dialog-bottom":"CodeMirror-dialog CodeMirror-dialog-top","string"==typeof o?t.innerHTML=o:t.appendChild(o),s.addClass(e,"dialog-opened"),t}function p(e,o){e.state.currentNotificationClose&&e.state.currentNotificationClose(),e.state.currentNotificationClose=o}s.defineExtension("openDialog",function(e,o,n){n=n||{},p(this,null);var t=f(this,e,n.bottom),i=!1,r=this;function u(e){"string"==typeof e?l.value=e:i||(i=!0,s.rmClass(t.parentNode,"dialog-opened"),t.parentNode.removeChild(t),r.focus(),n.onClose&&n.onClose(t))}var l=t.getElementsByTagName("input")[0];return l?(l.focus(),n.value&&(l.value=n.value,!1!==n.selectValueOnOpen)&&l.select(),n.onInput&&s.on(l,"input",function(e){n.onInput(e,l.value,u)}),n.onKeyUp&&s.on(l,"keyup",function(e){n.onKeyUp(e,l.value,u)}),s.on(l,"keydown",function(e){n&&n.onKeyDown&&n.onKeyDown(e,l.value,u)||((27==e.keyCode||!1!==n.closeOnEnter&&13==e.keyCode)&&(l.blur(),s.e_stop(e),u()),13==e.keyCode&&o(l.value,e))}),!1!==n.closeOnBlur&&s.on(t,"focusout",function(e){null!==e.relatedTarget&&u()})):(e=t.getElementsByTagName("button")[0])&&(s.on(e,"click",function(){u(),r.focus()}),!1!==n.closeOnBlur&&s.on(e,"blur",u),e.focus()),u}),s.defineExtension("openConfirm",function(e,o,n){p(this,null);var t=f(this,e,n&&n.bottom),i=t.getElementsByTagName("button"),r=!1,u=this,l=1;function a(){r||(r=!0,s.rmClass(t.parentNode,"dialog-opened"),t.parentNode.removeChild(t),u.focus())}i[0].focus();for(var c=0;c<i.length;++c){var d=i[c];!function(o){s.on(d,"click",function(e){s.e_preventDefault(e),a(),o&&o(u)})}(o[c]),s.on(d,"blur",function(){--l,setTimeout(function(){l<=0&&a()},200)}),s.on(d,"focus",function(){++l})}}),s.defineExtension("openNotification",function(e,o){p(this,r);var n,t=f(this,e,o&&o.bottom),i=!1,e=o&&void 0!==o.duration?o.duration:5e3;function r(){i||(i=!0,clearTimeout(n),s.rmClass(t.parentNode,"dialog-opened"),t.parentNode.removeChild(t))}return s.on(t,"click",function(e){s.e_preventDefault(e),r()}),e&&(n=setTimeout(r,e)),r})});
|
2
deps/codemirror/htmlmixed.min.js
vendored
2
deps/codemirror/htmlmixed.min.js
vendored
@ -1 +1 @@
|
||||
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror"),require("../xml/xml"),require("../javascript/javascript"),require("../css/css")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","../xml/xml","../javascript/javascript","../css/css"],t):t(CodeMirror)}(function(m){"use strict";var l={script:[["lang",/(javascript|babel)/i,"javascript"],["type",/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i,"javascript"],["type",/./,"text/plain"],[null,null,"javascript"]],style:[["lang",/^css$/i,"css"],["type",/^(text\/)?(x-)?(stylesheet|css)$/i,"css"],["type",/./,"text/plain"],[null,null,"css"]]};var a={};function d(t,e){e=t.match(a[t=e]||(a[t]=new RegExp("\\s+"+t+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*")));return e?/^\s*(.*?)\s*$/.exec(e[2])[1]:""}function g(t,e){return new RegExp((e?"^":"")+"</\\s*"+t+"\\s*>","i")}function o(t,e){for(var a in t)for(var n=e[a]||(e[a]=[]),l=t[a],o=l.length-1;0<=o;o--)n.unshift(l[o])}m.defineMode("htmlmixed",function(i,t){var c=m.getMode(i,{name:"xml",htmlMode:!0,multilineTagIndentFactor:t.multilineTagIndentFactor,multilineTagIndentPastTag:t.multilineTagIndentPastTag,allowMissingTagName:t.allowMissingTagName}),s={},e=t&&t.tags,a=t&&t.scriptTypes;if(o(l,s),e&&o(e,s),a)for(var n=a.length-1;0<=n;n--)s.script.unshift(["type",a[n].matches,a[n].mode]);function u(t,e){var a,o,r,n=c.token(t,e.htmlState),l=/\btag\b/.test(n);return l&&!/[<>\s\/]/.test(t.current())&&(a=e.htmlState.tagName&&e.htmlState.tagName.toLowerCase())&&s.hasOwnProperty(a)?e.inTag=a+" ":e.inTag&&l&&/>$/.test(t.current())?(a=/^([\S]+) (.*)/.exec(e.inTag),e.inTag=null,l=">"==t.current()&&function(t,e){for(var a=0;a<t.length;a++){var n=t[a];if(!n[0]||n[1].test(d(e,n[0])))return n[2]}}(s[a[1]],a[2]),l=m.getMode(i,l),o=g(a[1],!0),r=g(a[1],!1),e.token=function(t,e){return t.match(o,!1)?(e.token=u,e.localState=e.localMode=null):(a=t,n=r,t=e.localMode.token(t,e.localState),e=a.current(),-1<(l=e.search(n))?a.backUp(e.length-l):e.match(/<\/?$/)&&(a.backUp(e.length),a.match(n,!1)||a.match(e)),t);var a,n,l},e.localMode=l,e.localState=m.startState(l,c.indent(e.htmlState,"",""))):e.inTag&&(e.inTag+=t.current(),t.eol()&&(e.inTag+=" ")),n}return{startState:function(){return{token:u,inTag:null,localMode:null,localState:null,htmlState:m.startState(c)}},copyState:function(t){var e;return t.localState&&(e=m.copyState(t.localMode,t.localState)),{token:t.token,inTag:t.inTag,localMode:t.localMode,localState:e,htmlState:m.copyState(c,t.htmlState)}},token:function(t,e){return e.token(t,e)},indent:function(t,e,a){return!t.localMode||/^\s*<\//.test(e)?c.indent(t.htmlState,e,a):t.localMode.indent?t.localMode.indent(t.localState,e,a):m.Pass},innerMode:function(t){return{state:t.localState||t.htmlState,mode:t.localMode||c}}}},"xml","javascript","css"),m.defineMIME("text/html","htmlmixed")});
|
||||
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror"),require("../xml/xml"),require("../javascript/javascript"),require("../css/css")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","../xml/xml","../javascript/javascript","../css/css"],t):t(CodeMirror)}(function(m){"use strict";var l={script:[["lang",/(javascript|babel)/i,"javascript"],["type",/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i,"javascript"],["type",/./,"text/plain"],[null,null,"javascript"]],style:[["lang",/^css$/i,"css"],["type",/^(text\/)?(x-)?(stylesheet|css)$/i,"css"],["type",/./,"text/plain"],[null,null,"css"]]};var a={};function d(t,e){e=t.match(a[t=e]||(a[t]=new RegExp("\\s+"+t+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*")));return e?/^\s*(.*?)\s*$/.exec(e[2])[1]:""}function g(t,e){return new RegExp((e?"^":"")+"</\\s*"+t+"\\s*>","i")}function o(t,e){for(var a in t)for(var n=e[a]||(e[a]=[]),l=t[a],o=l.length-1;0<=o;o--)n.unshift(l[o])}m.defineMode("htmlmixed",function(i,t){var c=m.getMode(i,{name:"xml",htmlMode:!0,multilineTagIndentFactor:t.multilineTagIndentFactor,multilineTagIndentPastTag:t.multilineTagIndentPastTag,allowMissingTagName:t.allowMissingTagName}),s={},e=t&&t.tags,a=t&&t.scriptTypes;if(o(l,s),e&&o(e,s),a)for(var n=a.length-1;0<=n;n--)s.script.unshift(["type",a[n].matches,a[n].mode]);function u(t,e){var a,o,r,n=c.token(t,e.htmlState),l=/\btag\b/.test(n);return l&&!/[<>\s\/]/.test(t.current())&&(a=e.htmlState.tagName&&e.htmlState.tagName.toLowerCase())&&s.hasOwnProperty(a)?e.inTag=a+" ":e.inTag&&l&&/>$/.test(t.current())?(a=/^([\S]+) (.*)/.exec(e.inTag),e.inTag=null,l=">"==t.current()&&function(t,e){for(var a=0;a<t.length;a++){var n=t[a];if(!n[0]||n[1].test(d(e,n[0])))return n[2]}}(s[a[1]],a[2]),l=m.getMode(i,l),o=g(a[1],!0),r=g(a[1],!1),e.token=function(t,e){return t.match(o,!1)?(e.token=u,e.localState=e.localMode=null):(a=t,n=r,t=e.localMode.token(t,e.localState),e=a.current(),-1<(l=e.search(n))?a.backUp(e.length-l):e.match(/<\/?$/)&&(a.backUp(e.length),a.match(n,!1)||a.match(e)),t);var a,n,l},e.localMode=l,e.localState=m.startState(l,c.indent(e.htmlState,"",""))):e.inTag&&(e.inTag+=t.current(),t.eol())&&(e.inTag+=" "),n}return{startState:function(){return{token:u,inTag:null,localMode:null,localState:null,htmlState:m.startState(c)}},copyState:function(t){var e;return t.localState&&(e=m.copyState(t.localMode,t.localState)),{token:t.token,inTag:t.inTag,localMode:t.localMode,localState:e,htmlState:m.copyState(c,t.htmlState)}},token:function(t,e){return e.token(t,e)},indent:function(t,e,a){return!t.localMode||/^\s*<\//.test(e)?c.indent(t.htmlState,e,a):t.localMode.indent?t.localMode.indent(t.localState,e,a):m.Pass},innerMode:function(t){return{state:t.localState||t.htmlState,mode:t.localMode||c}}}},"xml","javascript","css"),m.defineMIME("text/html","htmlmixed")});
|
2
deps/codemirror/javascript.min.js
vendored
2
deps/codemirror/javascript.min.js
vendored
File diff suppressed because one or more lines are too long
2
deps/codemirror/search.min.js
vendored
2
deps/codemirror/search.min.js
vendored
File diff suppressed because one or more lines are too long
2
deps/codemirror/searchcursor.min.js
vendored
2
deps/codemirror/searchcursor.min.js
vendored
File diff suppressed because one or more lines are too long
32
deps/codemirror/update.sh
vendored
32
deps/codemirror/update.sh
vendored
@ -1,20 +1,20 @@
|
||||
LINKS="
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/dialog/dialog.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/dialog/dialog.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/edit/trailingspace.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/lint/javascript-lint.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/scroll/annotatescrollbar.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/search/matchesonscrollbar.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/search/matchesonscrollbar.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/search/search.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/addon/search/searchcursor.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/codemirror.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/codemirror.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/mode/css/css.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/mode/htmlmixed/htmlmixed.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/mode/javascript/javascript.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/mode/xml/xml.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/theme/base16-dark.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/dialog/dialog.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/dialog/dialog.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/edit/trailingspace.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/lint/javascript-lint.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/scroll/annotatescrollbar.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/search/matchesonscrollbar.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/search/matchesonscrollbar.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/search/search.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/search/searchcursor.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/codemirror.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/codemirror.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/mode/css/css.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/mode/htmlmixed/htmlmixed.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/mode/javascript/javascript.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/mode/xml/xml.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/theme/base16-dark.min.css
|
||||
"
|
||||
|
||||
for link in $LINKS; do
|
||||
|
2
deps/codemirror/xml.min.js
vendored
2
deps/codemirror/xml.min.js
vendored
File diff suppressed because one or more lines are too long
1
deps/libuv/.readthedocs.yaml
vendored
1
deps/libuv/.readthedocs.yaml
vendored
@ -6,6 +6,7 @@ sphinx:
|
||||
fail_on_warning: false
|
||||
|
||||
build:
|
||||
os: "ubuntu-22.04"
|
||||
tools:
|
||||
python: "3.9"
|
||||
|
||||
|
6
deps/libuv/AUTHORS
vendored
6
deps/libuv/AUTHORS
vendored
@ -542,3 +542,9 @@ Lewis Russell <me@lewisr.dev>
|
||||
sivadeilra <arlie.davis@gmail.com>
|
||||
cui fliter <imcusg@gmail.com>
|
||||
Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
|
||||
Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
|
||||
Stefan Karpinski <stefan@karpinski.org>
|
||||
liuxiang88 <94350585+liuxiang88@users.noreply.github.com>
|
||||
Jeffrey H. Johnson <trnsz@pobox.com>
|
||||
Abdirahim Musse <33973272+abmusse@users.noreply.github.com>
|
||||
小明 <7737673+caobug@users.noreply.github.com>
|
||||
|
61
deps/libuv/ChangeLog
vendored
61
deps/libuv/ChangeLog
vendored
@ -1,4 +1,63 @@
|
||||
2023.05.19, Version 1.45.0 (Stable)
|
||||
2023.06.30, Version 1.46.0 (Stable)
|
||||
|
||||
Changes since version 1.45.0:
|
||||
|
||||
* Add SHA to ChangeLog (Santiago Gimeno)
|
||||
|
||||
* misc: update readthedocs config (Jameson Nash)
|
||||
|
||||
* test: remove erroneous RETURN_SKIP (Ben Noordhuis)
|
||||
|
||||
* android: disable io_uring support (Ben Noordhuis)
|
||||
|
||||
* linux: add some more iouring backed fs ops (Santiago Gimeno)
|
||||
|
||||
* build: add autoconf option for disable-maintainer-mode (Jameson Nash)
|
||||
|
||||
* fs: use WTF-8 on Windows (Stefan Karpinski)
|
||||
|
||||
* unix,win: replace QUEUE with struct uv__queue (Ben Noordhuis)
|
||||
|
||||
* linux: fs_read to use io_uring if iovcnt > IOV_MAX (Santiago Gimeno)
|
||||
|
||||
* ios: fix uv_getrusage() ru_maxrss calculation (Ben Noordhuis)
|
||||
|
||||
* include: update outdated code comment (Ben Noordhuis)
|
||||
|
||||
* linux: support abstract unix sockets (Ben Noordhuis)
|
||||
|
||||
* unix,win: add UV_PIPE_NO_TRUNCATE flag (Ben Noordhuis)
|
||||
|
||||
* unix: add loongarch support (liuxiang88)
|
||||
|
||||
* doc: add DPS8M to LINKS.md (Jeffrey H. Johnson)
|
||||
|
||||
* include: add EUNATCH errno mapping (Abdirahim Musse)
|
||||
|
||||
* src: don't run timers if loop is stopped/unref'd (Trevor Norris)
|
||||
|
||||
* win: fix -Wpointer-to-int-cast warning (Ben Noordhuis)
|
||||
|
||||
* test,win: fix -Wunused-variable warning (Ben Noordhuis)
|
||||
|
||||
* test,win: fix -Wformat warning (Ben Noordhuis)
|
||||
|
||||
* linux: work around io_uring IORING_OP_CLOSE bug (Ben Noordhuis)
|
||||
|
||||
* win: remove unused functions (Ben Noordhuis)
|
||||
|
||||
* bench: add bench to check uv_loop_alive (Trevor Norris)
|
||||
|
||||
* test: add uv_cancel test for threadpool (Trevor Norris)
|
||||
|
||||
* unix: skip prohibited syscalls on tvOS and watchOS (小明)
|
||||
|
||||
* unix,fs: make no_pwritev access thread-safe (Santiago Gimeno)
|
||||
|
||||
* unix: fix build for lower versions of Android (小明)
|
||||
|
||||
|
||||
2023.05.19, Version 1.45.0 (Stable), 96e05543f53b19d9642b4b0dd73b86ad3cea313e
|
||||
|
||||
Changes since version 1.44.2:
|
||||
|
||||
|
3
deps/libuv/LINKS.md
vendored
3
deps/libuv/LINKS.md
vendored
@ -6,7 +6,8 @@
|
||||
* [clearskies_core](https://github.com/larroy/clearskies_core): Clearskies file synchronization program. (C++11)
|
||||
* [CMake](https://cmake.org) open-source, cross-platform family of tools designed to build, test and package software
|
||||
* [Cocos-Engine](https://github.com/cocos/cocos-engine): The runtime framework for Cocos Creator editor.
|
||||
* [Coherence](https://github.com/liesware/coherence/): Cryptographic server for modern web apps.
|
||||
* [Coherence](https://github.com/liesware/coherence/): Cryptographic server for modern web apps.
|
||||
* [DPS8M](https://dps8m.gitlab.io): GE ∕ Honeywell ∕ Bull DPS‑8/M and 6180/L68 mainframe simulator.
|
||||
* [DPS-For-IoT](https://github.com/intel/dps-for-iot/wiki): Fully distributed publish/subscribe protocol.
|
||||
* [HashLink](https://github.com/HaxeFoundation/hashlink): Haxe run-time with libuv support included.
|
||||
* [Haywire](https://github.com/kellabyte/Haywire): Asynchronous HTTP server.
|
||||
|
3
deps/libuv/configure.ac
vendored
3
deps/libuv/configure.ac
vendored
@ -13,12 +13,13 @@
|
||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
AC_PREREQ(2.57)
|
||||
AC_INIT([libuv], [1.45.0], [https://github.com/libuv/libuv/issues])
|
||||
AC_INIT([libuv], [1.46.0], [https://github.com/libuv/libuv/issues])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
m4_include([m4/libuv-extra-automake-flags.m4])
|
||||
m4_include([m4/as_case.m4])
|
||||
m4_include([m4/libuv-check-flags.m4])
|
||||
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects] UV_EXTRA_AUTOMAKE_FLAGS)
|
||||
AM_MAINTAINER_MODE([enable]) # pass --disable-maintainer-mode if autotools may be unavailable
|
||||
AC_CANONICAL_HOST
|
||||
AC_ENABLE_SHARED
|
||||
AC_ENABLE_STATIC
|
||||
|
3
deps/libuv/docs/src/errors.rst
vendored
3
deps/libuv/docs/src/errors.rst
vendored
@ -339,6 +339,9 @@ Error constants
|
||||
|
||||
socket type not supported
|
||||
|
||||
.. c:macro:: UV_EUNATCH
|
||||
|
||||
protocol driver not attached
|
||||
|
||||
API
|
||||
---
|
||||
|
54
deps/libuv/docs/src/pipe.rst
vendored
54
deps/libuv/docs/src/pipe.rst
vendored
@ -55,17 +55,61 @@ API
|
||||
|
||||
Bind the pipe to a file path (Unix) or a name (Windows).
|
||||
|
||||
Does not support Linux abstract namespace sockets,
|
||||
unlike :c:func:`uv_pipe_bind2`.
|
||||
|
||||
Alias for ``uv_pipe_bind2(handle, name, strlen(name), 0)``.
|
||||
|
||||
.. note::
|
||||
Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes, typically between
|
||||
92 and 108 bytes.
|
||||
Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes,
|
||||
typically between 92 and 108 bytes.
|
||||
|
||||
.. c:function:: int uv_pipe_bind2(uv_pipe_t* handle, const char* name, size_t namelen, unsigned int flags)
|
||||
|
||||
Bind the pipe to a file path (Unix) or a name (Windows).
|
||||
|
||||
``flags`` must be zero or ``UV_PIPE_NO_TRUNCATE``. Returns ``UV_EINVAL``
|
||||
for unsupported flags without performing the bind operation.
|
||||
|
||||
Supports Linux abstract namespace sockets. ``namelen`` must include
|
||||
the leading nul byte but not the trailing nul byte.
|
||||
|
||||
.. versionadded:: 1.46.0
|
||||
|
||||
.. note::
|
||||
Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes,
|
||||
typically between 92 and 108 bytes, unless the ``UV_PIPE_NO_TRUNCATE``
|
||||
flag is specified, in which case an ``UV_EINVAL`` error is returned.
|
||||
|
||||
.. c:function:: void uv_pipe_connect(uv_connect_t* req, uv_pipe_t* handle, const char* name, uv_connect_cb cb)
|
||||
|
||||
Connect to the Unix domain socket or the named pipe.
|
||||
Connect to the Unix domain socket or the Windows named pipe.
|
||||
|
||||
Does not support Linux abstract namespace sockets,
|
||||
unlike :c:func:`uv_pipe_connect2`.
|
||||
|
||||
Alias for ``uv_pipe_connect2(req, handle, name, strlen(name), 0, cb)``.
|
||||
|
||||
.. note::
|
||||
Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes, typically between
|
||||
92 and 108 bytes.
|
||||
Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes,
|
||||
typically between 92 and 108 bytes.
|
||||
|
||||
.. c:function:: void uv_pipe_connect2(uv_connect_t* req, uv_pipe_t* handle, const char* name, size_t namelen, unsigned int flags, uv_connect_cb cb)
|
||||
|
||||
Connect to the Unix domain socket or the Windows named pipe.
|
||||
|
||||
``flags`` must be zero or ``UV_PIPE_NO_TRUNCATE``. Returns ``UV_EINVAL``
|
||||
for unsupported flags without performing the connect operation.
|
||||
|
||||
Supports Linux abstract namespace sockets. ``namelen`` must include
|
||||
the leading nul byte but not the trailing nul byte.
|
||||
|
||||
.. versionadded:: 1.46.0
|
||||
|
||||
.. note::
|
||||
Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes,
|
||||
typically between 92 and 108 bytes, unless the ``UV_PIPE_NO_TRUNCATE``
|
||||
flag is specified, in which case an ``UV_EINVAL`` error is returned.
|
||||
|
||||
.. c:function:: int uv_pipe_getsockname(const uv_pipe_t* handle, char* buffer, size_t* size)
|
||||
|
||||
|
29
deps/libuv/include/uv.h
vendored
29
deps/libuv/include/uv.h
vendored
@ -59,6 +59,12 @@ extern "C" {
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Internal type, do not use. */
|
||||
struct uv__queue {
|
||||
struct uv__queue* next;
|
||||
struct uv__queue* prev;
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
# include "uv/win.h"
|
||||
#else
|
||||
@ -150,6 +156,7 @@ extern "C" {
|
||||
XX(EILSEQ, "illegal byte sequence") \
|
||||
XX(ESOCKTNOSUPPORT, "socket type not supported") \
|
||||
XX(ENODATA, "no data available") \
|
||||
XX(EUNATCH, "protocol driver not attached") \
|
||||
|
||||
#define UV_HANDLE_TYPE_MAP(XX) \
|
||||
XX(ASYNC, async) \
|
||||
@ -283,13 +290,13 @@ UV_EXTERN int uv_loop_init(uv_loop_t* loop);
|
||||
UV_EXTERN int uv_loop_close(uv_loop_t* loop);
|
||||
/*
|
||||
* NOTE:
|
||||
* This function is DEPRECATED (to be removed after 0.12), users should
|
||||
* This function is DEPRECATED, users should
|
||||
* allocate the loop manually and use uv_loop_init instead.
|
||||
*/
|
||||
UV_EXTERN uv_loop_t* uv_loop_new(void);
|
||||
/*
|
||||
* NOTE:
|
||||
* This function is DEPRECATED (to be removed after 0.12). Users should use
|
||||
* This function is DEPRECATED. Users should use
|
||||
* uv_loop_close and free the memory manually instead.
|
||||
*/
|
||||
UV_EXTERN void uv_loop_delete(uv_loop_t*);
|
||||
@ -459,7 +466,7 @@ struct uv_shutdown_s {
|
||||
uv_handle_type type; \
|
||||
/* private */ \
|
||||
uv_close_cb close_cb; \
|
||||
void* handle_queue[2]; \
|
||||
struct uv__queue handle_queue; \
|
||||
union { \
|
||||
int fd; \
|
||||
void* reserved[4]; \
|
||||
@ -801,6 +808,10 @@ inline int uv_tty_set_mode(uv_tty_t* handle, int mode) {
|
||||
|
||||
UV_EXTERN uv_handle_type uv_guess_handle(uv_file file);
|
||||
|
||||
enum {
|
||||
UV_PIPE_NO_TRUNCATE = 1u << 0
|
||||
};
|
||||
|
||||
/*
|
||||
* uv_pipe_t is a subclass of uv_stream_t.
|
||||
*
|
||||
@ -817,10 +828,20 @@ struct uv_pipe_s {
|
||||
UV_EXTERN int uv_pipe_init(uv_loop_t*, uv_pipe_t* handle, int ipc);
|
||||
UV_EXTERN int uv_pipe_open(uv_pipe_t*, uv_file file);
|
||||
UV_EXTERN int uv_pipe_bind(uv_pipe_t* handle, const char* name);
|
||||
UV_EXTERN int uv_pipe_bind2(uv_pipe_t* handle,
|
||||
const char* name,
|
||||
size_t namelen,
|
||||
unsigned int flags);
|
||||
UV_EXTERN void uv_pipe_connect(uv_connect_t* req,
|
||||
uv_pipe_t* handle,
|
||||
const char* name,
|
||||
uv_connect_cb cb);
|
||||
UV_EXTERN int uv_pipe_connect2(uv_connect_t* req,
|
||||
uv_pipe_t* handle,
|
||||
const char* name,
|
||||
size_t namelen,
|
||||
unsigned int flags,
|
||||
uv_connect_cb cb);
|
||||
UV_EXTERN int uv_pipe_getsockname(const uv_pipe_t* handle,
|
||||
char* buffer,
|
||||
size_t* size);
|
||||
@ -1849,7 +1870,7 @@ struct uv_loop_s {
|
||||
void* data;
|
||||
/* Loop reference counting. */
|
||||
unsigned int active_handles;
|
||||
void* handle_queue[2];
|
||||
struct uv__queue handle_queue;
|
||||
union {
|
||||
void* unused;
|
||||
unsigned int count;
|
||||
|
6
deps/libuv/include/uv/darwin.h
vendored
6
deps/libuv/include/uv/darwin.h
vendored
@ -40,7 +40,7 @@
|
||||
void* cf_state; \
|
||||
uv_mutex_t cf_mutex; \
|
||||
uv_sem_t cf_sem; \
|
||||
void* cf_signals[2]; \
|
||||
struct uv__queue cf_signals; \
|
||||
|
||||
#define UV_PLATFORM_FS_EVENT_FIELDS \
|
||||
uv__io_t event_watcher; \
|
||||
@ -48,8 +48,8 @@
|
||||
int realpath_len; \
|
||||
int cf_flags; \
|
||||
uv_async_t* cf_cb; \
|
||||
void* cf_events[2]; \
|
||||
void* cf_member[2]; \
|
||||
struct uv__queue cf_events; \
|
||||
struct uv__queue cf_member; \
|
||||
int cf_error; \
|
||||
uv_mutex_t cf_mutex; \
|
||||
|
||||
|
6
deps/libuv/include/uv/errno.h
vendored
6
deps/libuv/include/uv/errno.h
vendored
@ -468,4 +468,10 @@
|
||||
# define UV__ENODATA (-4024)
|
||||
#endif
|
||||
|
||||
#if defined(EUNATCH) && !defined(_WIN32)
|
||||
# define UV__EUNATCH UV__ERR(EUNATCH)
|
||||
#else
|
||||
# define UV__EUNATCH (-4023)
|
||||
#endif
|
||||
|
||||
#endif /* UV_ERRNO_H_ */
|
||||
|
2
deps/libuv/include/uv/linux.h
vendored
2
deps/libuv/include/uv/linux.h
vendored
@ -28,7 +28,7 @@
|
||||
int inotify_fd; \
|
||||
|
||||
#define UV_PLATFORM_FS_EVENT_FIELDS \
|
||||
void* watchers[2]; \
|
||||
struct uv__queue watchers; \
|
||||
int wd; \
|
||||
|
||||
#endif /* UV_LINUX_H */
|
||||
|
2
deps/libuv/include/uv/threadpool.h
vendored
2
deps/libuv/include/uv/threadpool.h
vendored
@ -31,7 +31,7 @@ struct uv__work {
|
||||
void (*work)(struct uv__work *w);
|
||||
void (*done)(struct uv__work *w, int status);
|
||||
struct uv_loop_s* loop;
|
||||
void* wq[2];
|
||||
struct uv__queue wq;
|
||||
};
|
||||
|
||||
#endif /* UV_THREADPOOL_H_ */
|
||||
|
48
deps/libuv/include/uv/unix.h
vendored
48
deps/libuv/include/uv/unix.h
vendored
@ -92,8 +92,8 @@ typedef struct uv__io_s uv__io_t;
|
||||
|
||||
struct uv__io_s {
|
||||
uv__io_cb cb;
|
||||
void* pending_queue[2];
|
||||
void* watcher_queue[2];
|
||||
struct uv__queue pending_queue;
|
||||
struct uv__queue watcher_queue;
|
||||
unsigned int pevents; /* Pending event mask i.e. mask at next tick. */
|
||||
unsigned int events; /* Current event mask. */
|
||||
int fd;
|
||||
@ -220,21 +220,21 @@ typedef struct {
|
||||
#define UV_LOOP_PRIVATE_FIELDS \
|
||||
unsigned long flags; \
|
||||
int backend_fd; \
|
||||
void* pending_queue[2]; \
|
||||
void* watcher_queue[2]; \
|
||||
struct uv__queue pending_queue; \
|
||||
struct uv__queue watcher_queue; \
|
||||
uv__io_t** watchers; \
|
||||
unsigned int nwatchers; \
|
||||
unsigned int nfds; \
|
||||
void* wq[2]; \
|
||||
struct uv__queue wq; \
|
||||
uv_mutex_t wq_mutex; \
|
||||
uv_async_t wq_async; \
|
||||
uv_rwlock_t cloexec_lock; \
|
||||
uv_handle_t* closing_handles; \
|
||||
void* process_handles[2]; \
|
||||
void* prepare_handles[2]; \
|
||||
void* check_handles[2]; \
|
||||
void* idle_handles[2]; \
|
||||
void* async_handles[2]; \
|
||||
struct uv__queue process_handles; \
|
||||
struct uv__queue prepare_handles; \
|
||||
struct uv__queue check_handles; \
|
||||
struct uv__queue idle_handles; \
|
||||
struct uv__queue async_handles; \
|
||||
void (*async_unused)(void); /* TODO(bnoordhuis) Remove in libuv v2. */ \
|
||||
uv__io_t async_io_watcher; \
|
||||
int async_wfd; \
|
||||
@ -257,7 +257,7 @@ typedef struct {
|
||||
#define UV_PRIVATE_REQ_TYPES /* empty */
|
||||
|
||||
#define UV_WRITE_PRIVATE_FIELDS \
|
||||
void* queue[2]; \
|
||||
struct uv__queue queue; \
|
||||
unsigned int write_index; \
|
||||
uv_buf_t* bufs; \
|
||||
unsigned int nbufs; \
|
||||
@ -265,12 +265,12 @@ typedef struct {
|
||||
uv_buf_t bufsml[4]; \
|
||||
|
||||
#define UV_CONNECT_PRIVATE_FIELDS \
|
||||
void* queue[2]; \
|
||||
struct uv__queue queue; \
|
||||
|
||||
#define UV_SHUTDOWN_PRIVATE_FIELDS /* empty */
|
||||
|
||||
#define UV_UDP_SEND_PRIVATE_FIELDS \
|
||||
void* queue[2]; \
|
||||
struct uv__queue queue; \
|
||||
struct sockaddr_storage addr; \
|
||||
unsigned int nbufs; \
|
||||
uv_buf_t* bufs; \
|
||||
@ -286,8 +286,8 @@ typedef struct {
|
||||
uv_connect_t *connect_req; \
|
||||
uv_shutdown_t *shutdown_req; \
|
||||
uv__io_t io_watcher; \
|
||||
void* write_queue[2]; \
|
||||
void* write_completed_queue[2]; \
|
||||
struct uv__queue write_queue; \
|
||||
struct uv__queue write_completed_queue; \
|
||||
uv_connection_cb connection_cb; \
|
||||
int delayed_error; \
|
||||
int accepted_fd; \
|
||||
@ -300,30 +300,30 @@ typedef struct {
|
||||
uv_alloc_cb alloc_cb; \
|
||||
uv_udp_recv_cb recv_cb; \
|
||||
uv__io_t io_watcher; \
|
||||
void* write_queue[2]; \
|
||||
void* write_completed_queue[2]; \
|
||||
struct uv__queue write_queue; \
|
||||
struct uv__queue write_completed_queue; \
|
||||
|
||||
#define UV_PIPE_PRIVATE_FIELDS \
|
||||
const char* pipe_fname; /* strdup'ed */
|
||||
const char* pipe_fname; /* NULL or strdup'ed */
|
||||
|
||||
#define UV_POLL_PRIVATE_FIELDS \
|
||||
uv__io_t io_watcher;
|
||||
|
||||
#define UV_PREPARE_PRIVATE_FIELDS \
|
||||
uv_prepare_cb prepare_cb; \
|
||||
void* queue[2]; \
|
||||
struct uv__queue queue; \
|
||||
|
||||
#define UV_CHECK_PRIVATE_FIELDS \
|
||||
uv_check_cb check_cb; \
|
||||
void* queue[2]; \
|
||||
struct uv__queue queue; \
|
||||
|
||||
#define UV_IDLE_PRIVATE_FIELDS \
|
||||
uv_idle_cb idle_cb; \
|
||||
void* queue[2]; \
|
||||
struct uv__queue queue; \
|
||||
|
||||
#define UV_ASYNC_PRIVATE_FIELDS \
|
||||
uv_async_cb async_cb; \
|
||||
void* queue[2]; \
|
||||
struct uv__queue queue; \
|
||||
int pending; \
|
||||
|
||||
#define UV_TIMER_PRIVATE_FIELDS \
|
||||
@ -352,7 +352,7 @@ typedef struct {
|
||||
int retcode;
|
||||
|
||||
#define UV_PROCESS_PRIVATE_FIELDS \
|
||||
void* queue[2]; \
|
||||
struct uv__queue queue; \
|
||||
int status; \
|
||||
|
||||
#define UV_FS_PRIVATE_FIELDS \
|
||||
@ -417,6 +417,8 @@ typedef struct {
|
||||
# define UV_FS_O_DIRECT 0x04000
|
||||
#elif defined(__linux__) && defined(__x86_64__)
|
||||
# define UV_FS_O_DIRECT 0x04000
|
||||
#elif defined(__linux__) && defined(__loongarch__)
|
||||
# define UV_FS_O_DIRECT 0x04000
|
||||
#elif defined(O_DIRECT)
|
||||
# define UV_FS_O_DIRECT O_DIRECT
|
||||
#else
|
||||
|
2
deps/libuv/include/uv/version.h
vendored
2
deps/libuv/include/uv/version.h
vendored
@ -31,7 +31,7 @@
|
||||
*/
|
||||
|
||||
#define UV_VERSION_MAJOR 1
|
||||
#define UV_VERSION_MINOR 45
|
||||
#define UV_VERSION_MINOR 46
|
||||
#define UV_VERSION_PATCH 0
|
||||
#define UV_VERSION_IS_RELEASE 1
|
||||
#define UV_VERSION_SUFFIX ""
|
||||
|
4
deps/libuv/include/uv/win.h
vendored
4
deps/libuv/include/uv/win.h
vendored
@ -357,7 +357,7 @@ typedef struct {
|
||||
/* Counter to started timer */ \
|
||||
uint64_t timer_counter; \
|
||||
/* Threadpool */ \
|
||||
void* wq[2]; \
|
||||
struct uv__queue wq; \
|
||||
uv_mutex_t wq_mutex; \
|
||||
uv_async_t wq_async;
|
||||
|
||||
@ -486,7 +486,7 @@ typedef struct {
|
||||
uint32_t payload_remaining; \
|
||||
uint64_t dummy; /* TODO: retained for ABI compat; remove this in v2.x. */ \
|
||||
} ipc_data_frame; \
|
||||
void* ipc_xfer_queue[2]; \
|
||||
struct uv__queue ipc_xfer_queue; \
|
||||
int ipc_xfer_queue_length; \
|
||||
uv_write_t* non_overlapped_writes_tail; \
|
||||
CRITICAL_SECTION readfile_thread_lock; \
|
||||
|
132
deps/libuv/src/queue.h
vendored
132
deps/libuv/src/queue.h
vendored
@ -18,91 +18,73 @@
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef void *QUEUE[2];
|
||||
#define uv__queue_data(pointer, type, field) \
|
||||
((type*) ((char*) (pointer) - offsetof(type, field)))
|
||||
|
||||
/* Private macros. */
|
||||
#define QUEUE_NEXT(q) (*(QUEUE **) &((*(q))[0]))
|
||||
#define QUEUE_PREV(q) (*(QUEUE **) &((*(q))[1]))
|
||||
#define QUEUE_PREV_NEXT(q) (QUEUE_NEXT(QUEUE_PREV(q)))
|
||||
#define QUEUE_NEXT_PREV(q) (QUEUE_PREV(QUEUE_NEXT(q)))
|
||||
#define uv__queue_foreach(q, h) \
|
||||
for ((q) = (h)->next; (q) != (h); (q) = (q)->next)
|
||||
|
||||
/* Public macros. */
|
||||
#define QUEUE_DATA(ptr, type, field) \
|
||||
((type *) ((char *) (ptr) - offsetof(type, field)))
|
||||
static inline void uv__queue_init(struct uv__queue* q) {
|
||||
q->next = q;
|
||||
q->prev = q;
|
||||
}
|
||||
|
||||
/* Important note: mutating the list while QUEUE_FOREACH is
|
||||
* iterating over its elements results in undefined behavior.
|
||||
*/
|
||||
#define QUEUE_FOREACH(q, h) \
|
||||
for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q))
|
||||
static inline int uv__queue_empty(const struct uv__queue* q) {
|
||||
return q == q->next;
|
||||
}
|
||||
|
||||
#define QUEUE_EMPTY(q) \
|
||||
((const QUEUE *) (q) == (const QUEUE *) QUEUE_NEXT(q))
|
||||
static inline struct uv__queue* uv__queue_head(const struct uv__queue* q) {
|
||||
return q->next;
|
||||
}
|
||||
|
||||
#define QUEUE_HEAD(q) \
|
||||
(QUEUE_NEXT(q))
|
||||
static inline struct uv__queue* uv__queue_next(const struct uv__queue* q) {
|
||||
return q->next;
|
||||
}
|
||||
|
||||
#define QUEUE_INIT(q) \
|
||||
do { \
|
||||
QUEUE_NEXT(q) = (q); \
|
||||
QUEUE_PREV(q) = (q); \
|
||||
} \
|
||||
while (0)
|
||||
static inline void uv__queue_add(struct uv__queue* h, struct uv__queue* n) {
|
||||
h->prev->next = n->next;
|
||||
n->next->prev = h->prev;
|
||||
h->prev = n->prev;
|
||||
h->prev->next = h;
|
||||
}
|
||||
|
||||
#define QUEUE_ADD(h, n) \
|
||||
do { \
|
||||
QUEUE_PREV_NEXT(h) = QUEUE_NEXT(n); \
|
||||
QUEUE_NEXT_PREV(n) = QUEUE_PREV(h); \
|
||||
QUEUE_PREV(h) = QUEUE_PREV(n); \
|
||||
QUEUE_PREV_NEXT(h) = (h); \
|
||||
} \
|
||||
while (0)
|
||||
static inline void uv__queue_split(struct uv__queue* h,
|
||||
struct uv__queue* q,
|
||||
struct uv__queue* n) {
|
||||
n->prev = h->prev;
|
||||
n->prev->next = n;
|
||||
n->next = q;
|
||||
h->prev = q->prev;
|
||||
h->prev->next = h;
|
||||
q->prev = n;
|
||||
}
|
||||
|
||||
#define QUEUE_SPLIT(h, q, n) \
|
||||
do { \
|
||||
QUEUE_PREV(n) = QUEUE_PREV(h); \
|
||||
QUEUE_PREV_NEXT(n) = (n); \
|
||||
QUEUE_NEXT(n) = (q); \
|
||||
QUEUE_PREV(h) = QUEUE_PREV(q); \
|
||||
QUEUE_PREV_NEXT(h) = (h); \
|
||||
QUEUE_PREV(q) = (n); \
|
||||
} \
|
||||
while (0)
|
||||
static inline void uv__queue_move(struct uv__queue* h, struct uv__queue* n) {
|
||||
if (uv__queue_empty(h))
|
||||
uv__queue_init(n);
|
||||
else
|
||||
uv__queue_split(h, h->next, n);
|
||||
}
|
||||
|
||||
#define QUEUE_MOVE(h, n) \
|
||||
do { \
|
||||
if (QUEUE_EMPTY(h)) \
|
||||
QUEUE_INIT(n); \
|
||||
else { \
|
||||
QUEUE* q = QUEUE_HEAD(h); \
|
||||
QUEUE_SPLIT(h, q, n); \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
static inline void uv__queue_insert_head(struct uv__queue* h,
|
||||
struct uv__queue* q) {
|
||||
q->next = h->next;
|
||||
q->prev = h;
|
||||
q->next->prev = q;
|
||||
h->next = q;
|
||||
}
|
||||
|
||||
#define QUEUE_INSERT_HEAD(h, q) \
|
||||
do { \
|
||||
QUEUE_NEXT(q) = QUEUE_NEXT(h); \
|
||||
QUEUE_PREV(q) = (h); \
|
||||
QUEUE_NEXT_PREV(q) = (q); \
|
||||
QUEUE_NEXT(h) = (q); \
|
||||
} \
|
||||
while (0)
|
||||
static inline void uv__queue_insert_tail(struct uv__queue* h,
|
||||
struct uv__queue* q) {
|
||||
q->next = h;
|
||||
q->prev = h->prev;
|
||||
q->prev->next = q;
|
||||
h->prev = q;
|
||||
}
|
||||
|
||||
#define QUEUE_INSERT_TAIL(h, q) \
|
||||
do { \
|
||||
QUEUE_NEXT(q) = (h); \
|
||||
QUEUE_PREV(q) = QUEUE_PREV(h); \
|
||||
QUEUE_PREV_NEXT(q) = (q); \
|
||||
QUEUE_PREV(h) = (q); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#define QUEUE_REMOVE(q) \
|
||||
do { \
|
||||
QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q); \
|
||||
QUEUE_NEXT_PREV(q) = QUEUE_PREV(q); \
|
||||
} \
|
||||
while (0)
|
||||
static inline void uv__queue_remove(struct uv__queue* q) {
|
||||
q->prev->next = q->next;
|
||||
q->next->prev = q->prev;
|
||||
}
|
||||
|
||||
#endif /* QUEUE_H_ */
|
||||
|
72
deps/libuv/src/threadpool.c
vendored
72
deps/libuv/src/threadpool.c
vendored
@ -37,10 +37,10 @@ static unsigned int slow_io_work_running;
|
||||
static unsigned int nthreads;
|
||||
static uv_thread_t* threads;
|
||||
static uv_thread_t default_threads[4];
|
||||
static QUEUE exit_message;
|
||||
static QUEUE wq;
|
||||
static QUEUE run_slow_work_message;
|
||||
static QUEUE slow_io_pending_wq;
|
||||
static struct uv__queue exit_message;
|
||||
static struct uv__queue wq;
|
||||
static struct uv__queue run_slow_work_message;
|
||||
static struct uv__queue slow_io_pending_wq;
|
||||
|
||||
static unsigned int slow_work_thread_threshold(void) {
|
||||
return (nthreads + 1) / 2;
|
||||
@ -56,7 +56,7 @@ static void uv__cancelled(struct uv__work* w) {
|
||||
*/
|
||||
static void worker(void* arg) {
|
||||
struct uv__work* w;
|
||||
QUEUE* q;
|
||||
struct uv__queue* q;
|
||||
int is_slow_work;
|
||||
|
||||
uv_sem_post((uv_sem_t*) arg);
|
||||
@ -68,49 +68,49 @@ static void worker(void* arg) {
|
||||
|
||||
/* Keep waiting while either no work is present or only slow I/O
|
||||
and we're at the threshold for that. */
|
||||
while (QUEUE_EMPTY(&wq) ||
|
||||
(QUEUE_HEAD(&wq) == &run_slow_work_message &&
|
||||
QUEUE_NEXT(&run_slow_work_message) == &wq &&
|
||||
while (uv__queue_empty(&wq) ||
|
||||
(uv__queue_head(&wq) == &run_slow_work_message &&
|
||||
uv__queue_next(&run_slow_work_message) == &wq &&
|
||||
slow_io_work_running >= slow_work_thread_threshold())) {
|
||||
idle_threads += 1;
|
||||
uv_cond_wait(&cond, &mutex);
|
||||
idle_threads -= 1;
|
||||
}
|
||||
|
||||
q = QUEUE_HEAD(&wq);
|
||||
q = uv__queue_head(&wq);
|
||||
if (q == &exit_message) {
|
||||
uv_cond_signal(&cond);
|
||||
uv_mutex_unlock(&mutex);
|
||||
break;
|
||||
}
|
||||
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INIT(q); /* Signal uv_cancel() that the work req is executing. */
|
||||
uv__queue_remove(q);
|
||||
uv__queue_init(q); /* Signal uv_cancel() that the work req is executing. */
|
||||
|
||||
is_slow_work = 0;
|
||||
if (q == &run_slow_work_message) {
|
||||
/* If we're at the slow I/O threshold, re-schedule until after all
|
||||
other work in the queue is done. */
|
||||
if (slow_io_work_running >= slow_work_thread_threshold()) {
|
||||
QUEUE_INSERT_TAIL(&wq, q);
|
||||
uv__queue_insert_tail(&wq, q);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If we encountered a request to run slow I/O work but there is none
|
||||
to run, that means it's cancelled => Start over. */
|
||||
if (QUEUE_EMPTY(&slow_io_pending_wq))
|
||||
if (uv__queue_empty(&slow_io_pending_wq))
|
||||
continue;
|
||||
|
||||
is_slow_work = 1;
|
||||
slow_io_work_running++;
|
||||
|
||||
q = QUEUE_HEAD(&slow_io_pending_wq);
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INIT(q);
|
||||
q = uv__queue_head(&slow_io_pending_wq);
|
||||
uv__queue_remove(q);
|
||||
uv__queue_init(q);
|
||||
|
||||
/* If there is more slow I/O work, schedule it to be run as well. */
|
||||
if (!QUEUE_EMPTY(&slow_io_pending_wq)) {
|
||||
QUEUE_INSERT_TAIL(&wq, &run_slow_work_message);
|
||||
if (!uv__queue_empty(&slow_io_pending_wq)) {
|
||||
uv__queue_insert_tail(&wq, &run_slow_work_message);
|
||||
if (idle_threads > 0)
|
||||
uv_cond_signal(&cond);
|
||||
}
|
||||
@ -118,13 +118,13 @@ static void worker(void* arg) {
|
||||
|
||||
uv_mutex_unlock(&mutex);
|
||||
|
||||
w = QUEUE_DATA(q, struct uv__work, wq);
|
||||
w = uv__queue_data(q, struct uv__work, wq);
|
||||
w->work(w);
|
||||
|
||||
uv_mutex_lock(&w->loop->wq_mutex);
|
||||
w->work = NULL; /* Signal uv_cancel() that the work req is done
|
||||
executing. */
|
||||
QUEUE_INSERT_TAIL(&w->loop->wq, &w->wq);
|
||||
uv__queue_insert_tail(&w->loop->wq, &w->wq);
|
||||
uv_async_send(&w->loop->wq_async);
|
||||
uv_mutex_unlock(&w->loop->wq_mutex);
|
||||
|
||||
@ -139,12 +139,12 @@ static void worker(void* arg) {
|
||||
}
|
||||
|
||||
|
||||
static void post(QUEUE* q, enum uv__work_kind kind) {
|
||||
static void post(struct uv__queue* q, enum uv__work_kind kind) {
|
||||
uv_mutex_lock(&mutex);
|
||||
if (kind == UV__WORK_SLOW_IO) {
|
||||
/* Insert into a separate queue. */
|
||||
QUEUE_INSERT_TAIL(&slow_io_pending_wq, q);
|
||||
if (!QUEUE_EMPTY(&run_slow_work_message)) {
|
||||
uv__queue_insert_tail(&slow_io_pending_wq, q);
|
||||
if (!uv__queue_empty(&run_slow_work_message)) {
|
||||
/* Running slow I/O tasks is already scheduled => Nothing to do here.
|
||||
The worker that runs said other task will schedule this one as well. */
|
||||
uv_mutex_unlock(&mutex);
|
||||
@ -153,7 +153,7 @@ static void post(QUEUE* q, enum uv__work_kind kind) {
|
||||
q = &run_slow_work_message;
|
||||
}
|
||||
|
||||
QUEUE_INSERT_TAIL(&wq, q);
|
||||
uv__queue_insert_tail(&wq, q);
|
||||
if (idle_threads > 0)
|
||||
uv_cond_signal(&cond);
|
||||
uv_mutex_unlock(&mutex);
|
||||
@ -220,9 +220,9 @@ static void init_threads(void) {
|
||||
if (uv_mutex_init(&mutex))
|
||||
abort();
|
||||
|
||||
QUEUE_INIT(&wq);
|
||||
QUEUE_INIT(&slow_io_pending_wq);
|
||||
QUEUE_INIT(&run_slow_work_message);
|
||||
uv__queue_init(&wq);
|
||||
uv__queue_init(&slow_io_pending_wq);
|
||||
uv__queue_init(&run_slow_work_message);
|
||||
|
||||
if (uv_sem_init(&sem, 0))
|
||||
abort();
|
||||
@ -285,9 +285,9 @@ static int uv__work_cancel(uv_loop_t* loop, uv_req_t* req, struct uv__work* w) {
|
||||
uv_mutex_lock(&mutex);
|
||||
uv_mutex_lock(&w->loop->wq_mutex);
|
||||
|
||||
cancelled = !QUEUE_EMPTY(&w->wq) && w->work != NULL;
|
||||
cancelled = !uv__queue_empty(&w->wq) && w->work != NULL;
|
||||
if (cancelled)
|
||||
QUEUE_REMOVE(&w->wq);
|
||||
uv__queue_remove(&w->wq);
|
||||
|
||||
uv_mutex_unlock(&w->loop->wq_mutex);
|
||||
uv_mutex_unlock(&mutex);
|
||||
@ -297,7 +297,7 @@ static int uv__work_cancel(uv_loop_t* loop, uv_req_t* req, struct uv__work* w) {
|
||||
|
||||
w->work = uv__cancelled;
|
||||
uv_mutex_lock(&loop->wq_mutex);
|
||||
QUEUE_INSERT_TAIL(&loop->wq, &w->wq);
|
||||
uv__queue_insert_tail(&loop->wq, &w->wq);
|
||||
uv_async_send(&loop->wq_async);
|
||||
uv_mutex_unlock(&loop->wq_mutex);
|
||||
|
||||
@ -308,21 +308,21 @@ static int uv__work_cancel(uv_loop_t* loop, uv_req_t* req, struct uv__work* w) {
|
||||
void uv__work_done(uv_async_t* handle) {
|
||||
struct uv__work* w;
|
||||
uv_loop_t* loop;
|
||||
QUEUE* q;
|
||||
QUEUE wq;
|
||||
struct uv__queue* q;
|
||||
struct uv__queue wq;
|
||||
int err;
|
||||
int nevents;
|
||||
|
||||
loop = container_of(handle, uv_loop_t, wq_async);
|
||||
uv_mutex_lock(&loop->wq_mutex);
|
||||
QUEUE_MOVE(&loop->wq, &wq);
|
||||
uv__queue_move(&loop->wq, &wq);
|
||||
uv_mutex_unlock(&loop->wq_mutex);
|
||||
|
||||
nevents = 0;
|
||||
|
||||
while (!QUEUE_EMPTY(&wq)) {
|
||||
q = QUEUE_HEAD(&wq);
|
||||
QUEUE_REMOVE(q);
|
||||
while (!uv__queue_empty(&wq)) {
|
||||
q = uv__queue_head(&wq);
|
||||
uv__queue_remove(q);
|
||||
|
||||
w = container_of(q, struct uv__work, wq);
|
||||
err = (w->work == uv__cancelled) ? UV_ECANCELED : 0;
|
||||
|
14
deps/libuv/src/unix/aix.c
vendored
14
deps/libuv/src/unix/aix.c
vendored
@ -136,7 +136,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
|
||||
struct pollfd pqry;
|
||||
struct pollfd* pe;
|
||||
struct poll_ctl pc;
|
||||
QUEUE* q;
|
||||
struct uv__queue* q;
|
||||
uv__io_t* w;
|
||||
uint64_t base;
|
||||
uint64_t diff;
|
||||
@ -151,18 +151,18 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
|
||||
int reset_timeout;
|
||||
|
||||
if (loop->nfds == 0) {
|
||||
assert(QUEUE_EMPTY(&loop->watcher_queue));
|
||||
assert(uv__queue_empty(&loop->watcher_queue));
|
||||
return;
|
||||
}
|
||||
|
||||
lfields = uv__get_internal_fields(loop);
|
||||
|
||||
while (!QUEUE_EMPTY(&loop->watcher_queue)) {
|
||||
q = QUEUE_HEAD(&loop->watcher_queue);
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INIT(q);
|
||||
while (!uv__queue_empty(&loop->watcher_queue)) {
|
||||
q = uv__queue_head(&loop->watcher_queue);
|
||||
uv__queue_remove(q);
|
||||
uv__queue_init(q);
|
||||
|
||||
w = QUEUE_DATA(q, uv__io_t, watcher_queue);
|
||||
w = uv__queue_data(q, uv__io_t, watcher_queue);
|
||||
assert(w->pevents != 0);
|
||||
assert(w->fd >= 0);
|
||||
assert(w->fd < (int) loop->nwatchers);
|
||||
|
52
deps/libuv/src/unix/async.c
vendored
52
deps/libuv/src/unix/async.c
vendored
@ -55,7 +55,7 @@ int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) {
|
||||
handle->pending = 0;
|
||||
handle->u.fd = 0; /* This will be used as a busy flag. */
|
||||
|
||||
QUEUE_INSERT_TAIL(&loop->async_handles, &handle->queue);
|
||||
uv__queue_insert_tail(&loop->async_handles, &handle->queue);
|
||||
uv__handle_start(handle);
|
||||
|
||||
return 0;
|
||||
@ -124,7 +124,7 @@ static void uv__async_spin(uv_async_t* handle) {
|
||||
|
||||
void uv__async_close(uv_async_t* handle) {
|
||||
uv__async_spin(handle);
|
||||
QUEUE_REMOVE(&handle->queue);
|
||||
uv__queue_remove(&handle->queue);
|
||||
uv__handle_stop(handle);
|
||||
}
|
||||
|
||||
@ -132,8 +132,8 @@ void uv__async_close(uv_async_t* handle) {
|
||||
static void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
|
||||
char buf[1024];
|
||||
ssize_t r;
|
||||
QUEUE queue;
|
||||
QUEUE* q;
|
||||
struct uv__queue queue;
|
||||
struct uv__queue* q;
|
||||
uv_async_t* h;
|
||||
_Atomic int *pending;
|
||||
|
||||
@ -157,13 +157,13 @@ static void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
|
||||
abort();
|
||||
}
|
||||
|
||||
QUEUE_MOVE(&loop->async_handles, &queue);
|
||||
while (!QUEUE_EMPTY(&queue)) {
|
||||
q = QUEUE_HEAD(&queue);
|
||||
h = QUEUE_DATA(q, uv_async_t, queue);
|
||||
uv__queue_move(&loop->async_handles, &queue);
|
||||
while (!uv__queue_empty(&queue)) {
|
||||
q = uv__queue_head(&queue);
|
||||
h = uv__queue_data(q, uv_async_t, queue);
|
||||
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INSERT_TAIL(&loop->async_handles, q);
|
||||
uv__queue_remove(q);
|
||||
uv__queue_insert_tail(&loop->async_handles, q);
|
||||
|
||||
/* Atomically fetch and clear pending flag */
|
||||
pending = (_Atomic int*) &h->pending;
|
||||
@ -241,8 +241,8 @@ static int uv__async_start(uv_loop_t* loop) {
|
||||
|
||||
|
||||
void uv__async_stop(uv_loop_t* loop) {
|
||||
QUEUE queue;
|
||||
QUEUE* q;
|
||||
struct uv__queue queue;
|
||||
struct uv__queue* q;
|
||||
uv_async_t* h;
|
||||
|
||||
if (loop->async_io_watcher.fd == -1)
|
||||
@ -251,13 +251,13 @@ void uv__async_stop(uv_loop_t* loop) {
|
||||
/* Make sure no other thread is accessing the async handle fd after the loop
|
||||
* cleanup.
|
||||
*/
|
||||
QUEUE_MOVE(&loop->async_handles, &queue);
|
||||
while (!QUEUE_EMPTY(&queue)) {
|
||||
q = QUEUE_HEAD(&queue);
|
||||
h = QUEUE_DATA(q, uv_async_t, queue);
|
||||
uv__queue_move(&loop->async_handles, &queue);
|
||||
while (!uv__queue_empty(&queue)) {
|
||||
q = uv__queue_head(&queue);
|
||||
h = uv__queue_data(q, uv_async_t, queue);
|
||||
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INSERT_TAIL(&loop->async_handles, q);
|
||||
uv__queue_remove(q);
|
||||
uv__queue_insert_tail(&loop->async_handles, q);
|
||||
|
||||
uv__async_spin(h);
|
||||
}
|
||||
@ -275,20 +275,20 @@ void uv__async_stop(uv_loop_t* loop) {
|
||||
|
||||
|
||||
int uv__async_fork(uv_loop_t* loop) {
|
||||
QUEUE queue;
|
||||
QUEUE* q;
|
||||
struct uv__queue queue;
|
||||
struct uv__queue* q;
|
||||
uv_async_t* h;
|
||||
|
||||
if (loop->async_io_watcher.fd == -1) /* never started */
|
||||
return 0;
|
||||
|
||||
QUEUE_MOVE(&loop->async_handles, &queue);
|
||||
while (!QUEUE_EMPTY(&queue)) {
|
||||
q = QUEUE_HEAD(&queue);
|
||||
h = QUEUE_DATA(q, uv_async_t, queue);
|
||||
uv__queue_move(&loop->async_handles, &queue);
|
||||
while (!uv__queue_empty(&queue)) {
|
||||
q = uv__queue_head(&queue);
|
||||
h = uv__queue_data(q, uv_async_t, queue);
|
||||
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INSERT_TAIL(&loop->async_handles, q);
|
||||
uv__queue_remove(q);
|
||||
uv__queue_insert_tail(&loop->async_handles, q);
|
||||
|
||||
/* The state of any thread that set pending is now likely corrupt in this
|
||||
* child because the user called fork, so just clear these flags and move
|
||||
|
67
deps/libuv/src/unix/core.c
vendored
67
deps/libuv/src/unix/core.c
vendored
@ -344,7 +344,7 @@ static void uv__finish_close(uv_handle_t* handle) {
|
||||
}
|
||||
|
||||
uv__handle_unref(handle);
|
||||
QUEUE_REMOVE(&handle->handle_queue);
|
||||
uv__queue_remove(&handle->handle_queue);
|
||||
|
||||
if (handle->close_cb) {
|
||||
handle->close_cb(handle);
|
||||
@ -380,7 +380,7 @@ int uv_backend_fd(const uv_loop_t* loop) {
|
||||
static int uv__loop_alive(const uv_loop_t* loop) {
|
||||
return uv__has_active_handles(loop) ||
|
||||
uv__has_active_reqs(loop) ||
|
||||
!QUEUE_EMPTY(&loop->pending_queue) ||
|
||||
!uv__queue_empty(&loop->pending_queue) ||
|
||||
loop->closing_handles != NULL;
|
||||
}
|
||||
|
||||
@ -389,8 +389,8 @@ static int uv__backend_timeout(const uv_loop_t* loop) {
|
||||
if (loop->stop_flag == 0 &&
|
||||
/* uv__loop_alive(loop) && */
|
||||
(uv__has_active_handles(loop) || uv__has_active_reqs(loop)) &&
|
||||
QUEUE_EMPTY(&loop->pending_queue) &&
|
||||
QUEUE_EMPTY(&loop->idle_handles) &&
|
||||
uv__queue_empty(&loop->pending_queue) &&
|
||||
uv__queue_empty(&loop->idle_handles) &&
|
||||
(loop->flags & UV_LOOP_REAP_CHILDREN) == 0 &&
|
||||
loop->closing_handles == NULL)
|
||||
return uv__next_timeout(loop);
|
||||
@ -399,7 +399,7 @@ static int uv__backend_timeout(const uv_loop_t* loop) {
|
||||
|
||||
|
||||
int uv_backend_timeout(const uv_loop_t* loop) {
|
||||
if (QUEUE_EMPTY(&loop->watcher_queue))
|
||||
if (uv__queue_empty(&loop->watcher_queue))
|
||||
return uv__backend_timeout(loop);
|
||||
/* Need to call uv_run to update the backend fd state. */
|
||||
return 0;
|
||||
@ -424,15 +424,15 @@ int uv_run(uv_loop_t* loop, uv_run_mode mode) {
|
||||
* while loop for UV_RUN_DEFAULT. Otherwise timers only need to be executed
|
||||
* once, which should be done after polling in order to maintain proper
|
||||
* execution order of the conceptual event loop. */
|
||||
if (mode == UV_RUN_DEFAULT) {
|
||||
if (r)
|
||||
uv__update_time(loop);
|
||||
if (mode == UV_RUN_DEFAULT && r != 0 && loop->stop_flag == 0) {
|
||||
uv__update_time(loop);
|
||||
uv__run_timers(loop);
|
||||
}
|
||||
|
||||
while (r != 0 && loop->stop_flag == 0) {
|
||||
can_sleep =
|
||||
QUEUE_EMPTY(&loop->pending_queue) && QUEUE_EMPTY(&loop->idle_handles);
|
||||
uv__queue_empty(&loop->pending_queue) &&
|
||||
uv__queue_empty(&loop->idle_handles);
|
||||
|
||||
uv__run_pending(loop);
|
||||
uv__run_idle(loop);
|
||||
@ -448,7 +448,7 @@ int uv_run(uv_loop_t* loop, uv_run_mode mode) {
|
||||
|
||||
/* Process immediate callbacks (e.g. write_cb) a small fixed number of
|
||||
* times to avoid loop starvation.*/
|
||||
for (r = 0; r < 8 && !QUEUE_EMPTY(&loop->pending_queue); r++)
|
||||
for (r = 0; r < 8 && !uv__queue_empty(&loop->pending_queue); r++)
|
||||
uv__run_pending(loop);
|
||||
|
||||
/* Run one final update on the provider_idle_time in case uv__io_poll
|
||||
@ -827,17 +827,17 @@ int uv_fileno(const uv_handle_t* handle, uv_os_fd_t* fd) {
|
||||
|
||||
|
||||
static void uv__run_pending(uv_loop_t* loop) {
|
||||
QUEUE* q;
|
||||
QUEUE pq;
|
||||
struct uv__queue* q;
|
||||
struct uv__queue pq;
|
||||
uv__io_t* w;
|
||||
|
||||
QUEUE_MOVE(&loop->pending_queue, &pq);
|
||||
uv__queue_move(&loop->pending_queue, &pq);
|
||||
|
||||
while (!QUEUE_EMPTY(&pq)) {
|
||||
q = QUEUE_HEAD(&pq);
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INIT(q);
|
||||
w = QUEUE_DATA(q, uv__io_t, pending_queue);
|
||||
while (!uv__queue_empty(&pq)) {
|
||||
q = uv__queue_head(&pq);
|
||||
uv__queue_remove(q);
|
||||
uv__queue_init(q);
|
||||
w = uv__queue_data(q, uv__io_t, pending_queue);
|
||||
w->cb(loop, w, POLLOUT);
|
||||
}
|
||||
}
|
||||
@ -892,8 +892,8 @@ static void maybe_resize(uv_loop_t* loop, unsigned int len) {
|
||||
void uv__io_init(uv__io_t* w, uv__io_cb cb, int fd) {
|
||||
assert(cb != NULL);
|
||||
assert(fd >= -1);
|
||||
QUEUE_INIT(&w->pending_queue);
|
||||
QUEUE_INIT(&w->watcher_queue);
|
||||
uv__queue_init(&w->pending_queue);
|
||||
uv__queue_init(&w->watcher_queue);
|
||||
w->cb = cb;
|
||||
w->fd = fd;
|
||||
w->events = 0;
|
||||
@ -919,8 +919,8 @@ void uv__io_start(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (QUEUE_EMPTY(&w->watcher_queue))
|
||||
QUEUE_INSERT_TAIL(&loop->watcher_queue, &w->watcher_queue);
|
||||
if (uv__queue_empty(&w->watcher_queue))
|
||||
uv__queue_insert_tail(&loop->watcher_queue, &w->watcher_queue);
|
||||
|
||||
if (loop->watchers[w->fd] == NULL) {
|
||||
loop->watchers[w->fd] = w;
|
||||
@ -945,8 +945,8 @@ void uv__io_stop(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
|
||||
w->pevents &= ~events;
|
||||
|
||||
if (w->pevents == 0) {
|
||||
QUEUE_REMOVE(&w->watcher_queue);
|
||||
QUEUE_INIT(&w->watcher_queue);
|
||||
uv__queue_remove(&w->watcher_queue);
|
||||
uv__queue_init(&w->watcher_queue);
|
||||
w->events = 0;
|
||||
|
||||
if (w == loop->watchers[w->fd]) {
|
||||
@ -955,14 +955,14 @@ void uv__io_stop(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
|
||||
loop->nfds--;
|
||||
}
|
||||
}
|
||||
else if (QUEUE_EMPTY(&w->watcher_queue))
|
||||
QUEUE_INSERT_TAIL(&loop->watcher_queue, &w->watcher_queue);
|
||||
else if (uv__queue_empty(&w->watcher_queue))
|
||||
uv__queue_insert_tail(&loop->watcher_queue, &w->watcher_queue);
|
||||
}
|
||||
|
||||
|
||||
void uv__io_close(uv_loop_t* loop, uv__io_t* w) {
|
||||
uv__io_stop(loop, w, POLLIN | POLLOUT | UV__POLLRDHUP | UV__POLLPRI);
|
||||
QUEUE_REMOVE(&w->pending_queue);
|
||||
uv__queue_remove(&w->pending_queue);
|
||||
|
||||
/* Remove stale events for this file descriptor */
|
||||
if (w->fd != -1)
|
||||
@ -971,8 +971,8 @@ void uv__io_close(uv_loop_t* loop, uv__io_t* w) {
|
||||
|
||||
|
||||
void uv__io_feed(uv_loop_t* loop, uv__io_t* w) {
|
||||
if (QUEUE_EMPTY(&w->pending_queue))
|
||||
QUEUE_INSERT_TAIL(&loop->pending_queue, &w->pending_queue);
|
||||
if (uv__queue_empty(&w->pending_queue))
|
||||
uv__queue_insert_tail(&loop->pending_queue, &w->pending_queue);
|
||||
}
|
||||
|
||||
|
||||
@ -1020,8 +1020,8 @@ int uv_getrusage(uv_rusage_t* rusage) {
|
||||
/* Most platforms report ru_maxrss in kilobytes; macOS and Solaris are
|
||||
* the outliers because of course they are.
|
||||
*/
|
||||
#if defined(__APPLE__) && !TARGET_OS_IPHONE
|
||||
rusage->ru_maxrss /= 1024; /* macOS reports bytes. */
|
||||
#if defined(__APPLE__)
|
||||
rusage->ru_maxrss /= 1024; /* macOS and iOS report bytes. */
|
||||
#elif defined(__sun)
|
||||
rusage->ru_maxrss /= getpagesize() / 1024; /* Solaris reports pages. */
|
||||
#endif
|
||||
@ -1271,6 +1271,10 @@ static int uv__getpwuid_r(uv_passwd_t *pwd, uid_t uid) {
|
||||
|
||||
|
||||
int uv_os_get_group(uv_group_t* grp, uv_uid_t gid) {
|
||||
#if defined(__ANDROID__) && __ANDROID_API__ < 24
|
||||
/* This function getgrgid_r() was added in Android N (level 24) */
|
||||
return UV_ENOSYS;
|
||||
#else
|
||||
struct group gp;
|
||||
struct group* result;
|
||||
char* buf;
|
||||
@ -1347,6 +1351,7 @@ int uv_os_get_group(uv_group_t* grp, uv_uid_t gid) {
|
||||
uv__free(buf);
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
64
deps/libuv/src/unix/fs.c
vendored
64
deps/libuv/src/unix/fs.c
vendored
@ -55,9 +55,13 @@
|
||||
# define HAVE_PREADV 0
|
||||
#endif
|
||||
|
||||
/* preadv() and pwritev() were added in Android N (level 24) */
|
||||
#if defined(__linux__) && !(defined(__ANDROID__) && __ANDROID_API__ < 24)
|
||||
# define TRY_PREADV 1
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
# include <sys/sendfile.h>
|
||||
# include <sys/utsname.h>
|
||||
#endif
|
||||
|
||||
#if defined(__sun)
|
||||
@ -457,7 +461,7 @@ static ssize_t uv__fs_preadv(uv_file fd,
|
||||
|
||||
|
||||
static ssize_t uv__fs_read(uv_fs_t* req) {
|
||||
#if defined(__linux__)
|
||||
#if TRY_PREADV
|
||||
static _Atomic int no_preadv;
|
||||
#endif
|
||||
unsigned int iovmax;
|
||||
@ -481,13 +485,13 @@ static ssize_t uv__fs_read(uv_fs_t* req) {
|
||||
#if HAVE_PREADV
|
||||
result = preadv(req->file, (struct iovec*) req->bufs, req->nbufs, req->off);
|
||||
#else
|
||||
# if defined(__linux__)
|
||||
# if TRY_PREADV
|
||||
if (atomic_load_explicit(&no_preadv, memory_order_relaxed)) retry:
|
||||
# endif
|
||||
{
|
||||
result = uv__fs_preadv(req->file, req->bufs, req->nbufs, req->off);
|
||||
}
|
||||
# if defined(__linux__)
|
||||
# if TRY_PREADV
|
||||
else {
|
||||
result = preadv(req->file,
|
||||
(struct iovec*) req->bufs,
|
||||
@ -899,31 +903,6 @@ out:
|
||||
|
||||
|
||||
#ifdef __linux__
|
||||
static unsigned uv__kernel_version(void) {
|
||||
static _Atomic unsigned cached_version;
|
||||
struct utsname u;
|
||||
unsigned version;
|
||||
unsigned major;
|
||||
unsigned minor;
|
||||
unsigned patch;
|
||||
|
||||
version = atomic_load_explicit(&cached_version, memory_order_relaxed);
|
||||
if (version != 0)
|
||||
return version;
|
||||
|
||||
if (-1 == uname(&u))
|
||||
return 0;
|
||||
|
||||
if (3 != sscanf(u.release, "%u.%u.%u", &major, &minor, &patch))
|
||||
return 0;
|
||||
|
||||
version = major * 65536 + minor * 256 + patch;
|
||||
atomic_store_explicit(&cached_version, version, memory_order_relaxed);
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
/* Pre-4.20 kernels have a bug where CephFS uses the RADOS copy-from command
|
||||
* in copy_file_range() when it shouldn't. There is no workaround except to
|
||||
* fall back to a regular copy.
|
||||
@ -1182,8 +1161,8 @@ static ssize_t uv__fs_lutime(uv_fs_t* req) {
|
||||
|
||||
|
||||
static ssize_t uv__fs_write(uv_fs_t* req) {
|
||||
#if defined(__linux__)
|
||||
static int no_pwritev;
|
||||
#if TRY_PREADV
|
||||
static _Atomic int no_pwritev;
|
||||
#endif
|
||||
ssize_t r;
|
||||
|
||||
@ -1211,20 +1190,20 @@ static ssize_t uv__fs_write(uv_fs_t* req) {
|
||||
#if HAVE_PREADV
|
||||
r = pwritev(req->file, (struct iovec*) req->bufs, req->nbufs, req->off);
|
||||
#else
|
||||
# if defined(__linux__)
|
||||
if (no_pwritev) retry:
|
||||
# if TRY_PREADV
|
||||
if (atomic_load_explicit(&no_pwritev, memory_order_relaxed)) retry:
|
||||
# endif
|
||||
{
|
||||
r = pwrite(req->file, req->bufs[0].base, req->bufs[0].len, req->off);
|
||||
}
|
||||
# if defined(__linux__)
|
||||
# if TRY_PREADV
|
||||
else {
|
||||
r = pwritev(req->file,
|
||||
(struct iovec*) req->bufs,
|
||||
req->nbufs,
|
||||
req->off);
|
||||
if (r == -1 && errno == ENOSYS) {
|
||||
no_pwritev = 1;
|
||||
atomic_store_explicit(&no_pwritev, 1, memory_order_relaxed);
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
@ -1926,6 +1905,9 @@ int uv_fs_link(uv_loop_t* loop,
|
||||
uv_fs_cb cb) {
|
||||
INIT(LINK);
|
||||
PATH2;
|
||||
if (cb != NULL)
|
||||
if (uv__iou_fs_link(loop, req))
|
||||
return 0;
|
||||
POST;
|
||||
}
|
||||
|
||||
@ -1938,6 +1920,9 @@ int uv_fs_mkdir(uv_loop_t* loop,
|
||||
INIT(MKDIR);
|
||||
PATH;
|
||||
req->mode = mode;
|
||||
if (cb != NULL)
|
||||
if (uv__iou_fs_mkdir(loop, req))
|
||||
return 0;
|
||||
POST;
|
||||
}
|
||||
|
||||
@ -2089,6 +2074,9 @@ int uv_fs_rename(uv_loop_t* loop,
|
||||
uv_fs_cb cb) {
|
||||
INIT(RENAME);
|
||||
PATH2;
|
||||
if (cb != NULL)
|
||||
if (uv__iou_fs_rename(loop, req))
|
||||
return 0;
|
||||
POST;
|
||||
}
|
||||
|
||||
@ -2135,6 +2123,9 @@ int uv_fs_symlink(uv_loop_t* loop,
|
||||
INIT(SYMLINK);
|
||||
PATH2;
|
||||
req->flags = flags;
|
||||
if (cb != NULL)
|
||||
if (uv__iou_fs_symlink(loop, req))
|
||||
return 0;
|
||||
POST;
|
||||
}
|
||||
|
||||
@ -2142,6 +2133,9 @@ int uv_fs_symlink(uv_loop_t* loop,
|
||||
int uv_fs_unlink(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
|
||||
INIT(UNLINK);
|
||||
PATH;
|
||||
if (cb != NULL)
|
||||
if (uv__iou_fs_unlink(loop, req))
|
||||
return 0;
|
||||
POST;
|
||||
}
|
||||
|
||||
|
84
deps/libuv/src/unix/fsevents.c
vendored
84
deps/libuv/src/unix/fsevents.c
vendored
@ -80,13 +80,13 @@ enum uv__cf_loop_signal_type_e {
|
||||
typedef enum uv__cf_loop_signal_type_e uv__cf_loop_signal_type_t;
|
||||
|
||||
struct uv__cf_loop_signal_s {
|
||||
QUEUE member;
|
||||
struct uv__queue member;
|
||||
uv_fs_event_t* handle;
|
||||
uv__cf_loop_signal_type_t type;
|
||||
};
|
||||
|
||||
struct uv__fsevents_event_s {
|
||||
QUEUE member;
|
||||
struct uv__queue member;
|
||||
int events;
|
||||
char path[1];
|
||||
};
|
||||
@ -98,7 +98,7 @@ struct uv__cf_loop_state_s {
|
||||
FSEventStreamRef fsevent_stream;
|
||||
uv_sem_t fsevent_sem;
|
||||
uv_mutex_t fsevent_mutex;
|
||||
void* fsevent_handles[2];
|
||||
struct uv__queue fsevent_handles;
|
||||
unsigned int fsevent_handle_count;
|
||||
};
|
||||
|
||||
@ -150,22 +150,22 @@ static void (*pFSEventStreamStop)(FSEventStreamRef);
|
||||
|
||||
#define UV__FSEVENTS_PROCESS(handle, block) \
|
||||
do { \
|
||||
QUEUE events; \
|
||||
QUEUE* q; \
|
||||
struct uv__queue events; \
|
||||
struct uv__queue* q; \
|
||||
uv__fsevents_event_t* event; \
|
||||
int err; \
|
||||
uv_mutex_lock(&(handle)->cf_mutex); \
|
||||
/* Split-off all events and empty original queue */ \
|
||||
QUEUE_MOVE(&(handle)->cf_events, &events); \
|
||||
uv__queue_move(&(handle)->cf_events, &events); \
|
||||
/* Get error (if any) and zero original one */ \
|
||||
err = (handle)->cf_error; \
|
||||
(handle)->cf_error = 0; \
|
||||
uv_mutex_unlock(&(handle)->cf_mutex); \
|
||||
/* Loop through events, deallocating each after processing */ \
|
||||
while (!QUEUE_EMPTY(&events)) { \
|
||||
q = QUEUE_HEAD(&events); \
|
||||
event = QUEUE_DATA(q, uv__fsevents_event_t, member); \
|
||||
QUEUE_REMOVE(q); \
|
||||
while (!uv__queue_empty(&events)) { \
|
||||
q = uv__queue_head(&events); \
|
||||
event = uv__queue_data(q, uv__fsevents_event_t, member); \
|
||||
uv__queue_remove(q); \
|
||||
/* NOTE: Checking uv__is_active() is required here, because handle \
|
||||
* callback may close handle and invoking it after it will lead to \
|
||||
* incorrect behaviour */ \
|
||||
@ -193,14 +193,14 @@ static void uv__fsevents_cb(uv_async_t* cb) {
|
||||
|
||||
/* Runs in CF thread, pushed event into handle's event list */
|
||||
static void uv__fsevents_push_event(uv_fs_event_t* handle,
|
||||
QUEUE* events,
|
||||
struct uv__queue* events,
|
||||
int err) {
|
||||
assert(events != NULL || err != 0);
|
||||
uv_mutex_lock(&handle->cf_mutex);
|
||||
|
||||
/* Concatenate two queues */
|
||||
if (events != NULL)
|
||||
QUEUE_ADD(&handle->cf_events, events);
|
||||
uv__queue_add(&handle->cf_events, events);
|
||||
|
||||
/* Propagate error */
|
||||
if (err != 0)
|
||||
@ -224,12 +224,12 @@ static void uv__fsevents_event_cb(const FSEventStreamRef streamRef,
|
||||
char* path;
|
||||
char* pos;
|
||||
uv_fs_event_t* handle;
|
||||
QUEUE* q;
|
||||
struct uv__queue* q;
|
||||
uv_loop_t* loop;
|
||||
uv__cf_loop_state_t* state;
|
||||
uv__fsevents_event_t* event;
|
||||
FSEventStreamEventFlags flags;
|
||||
QUEUE head;
|
||||
struct uv__queue head;
|
||||
|
||||
loop = info;
|
||||
state = loop->cf_state;
|
||||
@ -238,9 +238,9 @@ static void uv__fsevents_event_cb(const FSEventStreamRef streamRef,
|
||||
|
||||
/* For each handle */
|
||||
uv_mutex_lock(&state->fsevent_mutex);
|
||||
QUEUE_FOREACH(q, &state->fsevent_handles) {
|
||||
handle = QUEUE_DATA(q, uv_fs_event_t, cf_member);
|
||||
QUEUE_INIT(&head);
|
||||
uv__queue_foreach(q, &state->fsevent_handles) {
|
||||
handle = uv__queue_data(q, uv_fs_event_t, cf_member);
|
||||
uv__queue_init(&head);
|
||||
|
||||
/* Process and filter out events */
|
||||
for (i = 0; i < numEvents; i++) {
|
||||
@ -318,10 +318,10 @@ static void uv__fsevents_event_cb(const FSEventStreamRef streamRef,
|
||||
event->events = UV_CHANGE;
|
||||
}
|
||||
|
||||
QUEUE_INSERT_TAIL(&head, &event->member);
|
||||
uv__queue_insert_tail(&head, &event->member);
|
||||
}
|
||||
|
||||
if (!QUEUE_EMPTY(&head))
|
||||
if (!uv__queue_empty(&head))
|
||||
uv__fsevents_push_event(handle, &head, 0);
|
||||
}
|
||||
uv_mutex_unlock(&state->fsevent_mutex);
|
||||
@ -403,7 +403,7 @@ static void uv__fsevents_destroy_stream(uv__cf_loop_state_t* state) {
|
||||
static void uv__fsevents_reschedule(uv__cf_loop_state_t* state,
|
||||
uv_loop_t* loop,
|
||||
uv__cf_loop_signal_type_t type) {
|
||||
QUEUE* q;
|
||||
struct uv__queue* q;
|
||||
uv_fs_event_t* curr;
|
||||
CFArrayRef cf_paths;
|
||||
CFStringRef* paths;
|
||||
@ -446,9 +446,9 @@ static void uv__fsevents_reschedule(uv__cf_loop_state_t* state,
|
||||
|
||||
q = &state->fsevent_handles;
|
||||
for (; i < path_count; i++) {
|
||||
q = QUEUE_NEXT(q);
|
||||
q = uv__queue_next(q);
|
||||
assert(q != &state->fsevent_handles);
|
||||
curr = QUEUE_DATA(q, uv_fs_event_t, cf_member);
|
||||
curr = uv__queue_data(q, uv_fs_event_t, cf_member);
|
||||
|
||||
assert(curr->realpath != NULL);
|
||||
paths[i] =
|
||||
@ -486,8 +486,8 @@ final:
|
||||
|
||||
/* Broadcast error to all handles */
|
||||
uv_mutex_lock(&state->fsevent_mutex);
|
||||
QUEUE_FOREACH(q, &state->fsevent_handles) {
|
||||
curr = QUEUE_DATA(q, uv_fs_event_t, cf_member);
|
||||
uv__queue_foreach(q, &state->fsevent_handles) {
|
||||
curr = uv__queue_data(q, uv_fs_event_t, cf_member);
|
||||
uv__fsevents_push_event(curr, NULL, err);
|
||||
}
|
||||
uv_mutex_unlock(&state->fsevent_mutex);
|
||||
@ -606,7 +606,7 @@ static int uv__fsevents_loop_init(uv_loop_t* loop) {
|
||||
if (err)
|
||||
goto fail_sem_init;
|
||||
|
||||
QUEUE_INIT(&loop->cf_signals);
|
||||
uv__queue_init(&loop->cf_signals);
|
||||
|
||||
err = uv_sem_init(&state->fsevent_sem, 0);
|
||||
if (err)
|
||||
@ -616,7 +616,7 @@ static int uv__fsevents_loop_init(uv_loop_t* loop) {
|
||||
if (err)
|
||||
goto fail_fsevent_mutex_init;
|
||||
|
||||
QUEUE_INIT(&state->fsevent_handles);
|
||||
uv__queue_init(&state->fsevent_handles);
|
||||
state->fsevent_need_reschedule = 0;
|
||||
state->fsevent_handle_count = 0;
|
||||
|
||||
@ -675,7 +675,7 @@ fail_mutex_init:
|
||||
void uv__fsevents_loop_delete(uv_loop_t* loop) {
|
||||
uv__cf_loop_signal_t* s;
|
||||
uv__cf_loop_state_t* state;
|
||||
QUEUE* q;
|
||||
struct uv__queue* q;
|
||||
|
||||
if (loop->cf_state == NULL)
|
||||
return;
|
||||
@ -688,10 +688,10 @@ void uv__fsevents_loop_delete(uv_loop_t* loop) {
|
||||
uv_mutex_destroy(&loop->cf_mutex);
|
||||
|
||||
/* Free any remaining data */
|
||||
while (!QUEUE_EMPTY(&loop->cf_signals)) {
|
||||
q = QUEUE_HEAD(&loop->cf_signals);
|
||||
s = QUEUE_DATA(q, uv__cf_loop_signal_t, member);
|
||||
QUEUE_REMOVE(q);
|
||||
while (!uv__queue_empty(&loop->cf_signals)) {
|
||||
q = uv__queue_head(&loop->cf_signals);
|
||||
s = uv__queue_data(q, uv__cf_loop_signal_t, member);
|
||||
uv__queue_remove(q);
|
||||
uv__free(s);
|
||||
}
|
||||
|
||||
@ -735,22 +735,22 @@ static void* uv__cf_loop_runner(void* arg) {
|
||||
static void uv__cf_loop_cb(void* arg) {
|
||||
uv_loop_t* loop;
|
||||
uv__cf_loop_state_t* state;
|
||||
QUEUE* item;
|
||||
QUEUE split_head;
|
||||
struct uv__queue* item;
|
||||
struct uv__queue split_head;
|
||||
uv__cf_loop_signal_t* s;
|
||||
|
||||
loop = arg;
|
||||
state = loop->cf_state;
|
||||
|
||||
uv_mutex_lock(&loop->cf_mutex);
|
||||
QUEUE_MOVE(&loop->cf_signals, &split_head);
|
||||
uv__queue_move(&loop->cf_signals, &split_head);
|
||||
uv_mutex_unlock(&loop->cf_mutex);
|
||||
|
||||
while (!QUEUE_EMPTY(&split_head)) {
|
||||
item = QUEUE_HEAD(&split_head);
|
||||
QUEUE_REMOVE(item);
|
||||
while (!uv__queue_empty(&split_head)) {
|
||||
item = uv__queue_head(&split_head);
|
||||
uv__queue_remove(item);
|
||||
|
||||
s = QUEUE_DATA(item, uv__cf_loop_signal_t, member);
|
||||
s = uv__queue_data(item, uv__cf_loop_signal_t, member);
|
||||
|
||||
/* This was a termination signal */
|
||||
if (s->handle == NULL)
|
||||
@ -778,7 +778,7 @@ int uv__cf_loop_signal(uv_loop_t* loop,
|
||||
item->type = type;
|
||||
|
||||
uv_mutex_lock(&loop->cf_mutex);
|
||||
QUEUE_INSERT_TAIL(&loop->cf_signals, &item->member);
|
||||
uv__queue_insert_tail(&loop->cf_signals, &item->member);
|
||||
|
||||
state = loop->cf_state;
|
||||
assert(state != NULL);
|
||||
@ -807,7 +807,7 @@ int uv__fsevents_init(uv_fs_event_t* handle) {
|
||||
handle->realpath_len = strlen(handle->realpath);
|
||||
|
||||
/* Initialize event queue */
|
||||
QUEUE_INIT(&handle->cf_events);
|
||||
uv__queue_init(&handle->cf_events);
|
||||
handle->cf_error = 0;
|
||||
|
||||
/*
|
||||
@ -832,7 +832,7 @@ int uv__fsevents_init(uv_fs_event_t* handle) {
|
||||
/* Insert handle into the list */
|
||||
state = handle->loop->cf_state;
|
||||
uv_mutex_lock(&state->fsevent_mutex);
|
||||
QUEUE_INSERT_TAIL(&state->fsevent_handles, &handle->cf_member);
|
||||
uv__queue_insert_tail(&state->fsevent_handles, &handle->cf_member);
|
||||
state->fsevent_handle_count++;
|
||||
state->fsevent_need_reschedule = 1;
|
||||
uv_mutex_unlock(&state->fsevent_mutex);
|
||||
@ -872,7 +872,7 @@ int uv__fsevents_close(uv_fs_event_t* handle) {
|
||||
/* Remove handle from the list */
|
||||
state = handle->loop->cf_state;
|
||||
uv_mutex_lock(&state->fsevent_mutex);
|
||||
QUEUE_REMOVE(&handle->cf_member);
|
||||
uv__queue_remove(&handle->cf_member);
|
||||
state->fsevent_handle_count--;
|
||||
state->fsevent_need_reschedule = 1;
|
||||
uv_mutex_unlock(&state->fsevent_mutex);
|
||||
|
11
deps/libuv/src/unix/internal.h
vendored
11
deps/libuv/src/unix/internal.h
vendored
@ -335,20 +335,30 @@ int uv__iou_fs_close(uv_loop_t* loop, uv_fs_t* req);
|
||||
int uv__iou_fs_fsync_or_fdatasync(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
uint32_t fsync_flags);
|
||||
int uv__iou_fs_link(uv_loop_t* loop, uv_fs_t* req);
|
||||
int uv__iou_fs_mkdir(uv_loop_t* loop, uv_fs_t* req);
|
||||
int uv__iou_fs_open(uv_loop_t* loop, uv_fs_t* req);
|
||||
int uv__iou_fs_read_or_write(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
int is_read);
|
||||
int uv__iou_fs_rename(uv_loop_t* loop, uv_fs_t* req);
|
||||
int uv__iou_fs_statx(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
int is_fstat,
|
||||
int is_lstat);
|
||||
int uv__iou_fs_symlink(uv_loop_t* loop, uv_fs_t* req);
|
||||
int uv__iou_fs_unlink(uv_loop_t* loop, uv_fs_t* req);
|
||||
#else
|
||||
#define uv__iou_fs_close(loop, req) 0
|
||||
#define uv__iou_fs_fsync_or_fdatasync(loop, req, fsync_flags) 0
|
||||
#define uv__iou_fs_link(loop, req) 0
|
||||
#define uv__iou_fs_mkdir(loop, req) 0
|
||||
#define uv__iou_fs_open(loop, req) 0
|
||||
#define uv__iou_fs_read_or_write(loop, req, is_read) 0
|
||||
#define uv__iou_fs_rename(loop, req) 0
|
||||
#define uv__iou_fs_statx(loop, req, is_fstat, is_lstat) 0
|
||||
#define uv__iou_fs_symlink(loop, req) 0
|
||||
#define uv__iou_fs_unlink(loop, req) 0
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
@ -429,6 +439,7 @@ int uv__statx(int dirfd,
|
||||
struct uv__statx* statxbuf);
|
||||
void uv__statx_to_stat(const struct uv__statx* statxbuf, uv_stat_t* buf);
|
||||
ssize_t uv__getrandom(void* buf, size_t buflen, unsigned flags);
|
||||
unsigned uv__kernel_version(void);
|
||||
#endif
|
||||
|
||||
typedef int (*uv__peersockfunc)(int, struct sockaddr*, socklen_t*);
|
||||
|
18
deps/libuv/src/unix/kqueue.c
vendored
18
deps/libuv/src/unix/kqueue.c
vendored
@ -133,7 +133,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
|
||||
struct timespec spec;
|
||||
unsigned int nevents;
|
||||
unsigned int revents;
|
||||
QUEUE* q;
|
||||
struct uv__queue* q;
|
||||
uv__io_t* w;
|
||||
uv_process_t* process;
|
||||
sigset_t* pset;
|
||||
@ -152,19 +152,19 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
|
||||
int reset_timeout;
|
||||
|
||||
if (loop->nfds == 0) {
|
||||
assert(QUEUE_EMPTY(&loop->watcher_queue));
|
||||
assert(uv__queue_empty(&loop->watcher_queue));
|
||||
return;
|
||||
}
|
||||
|
||||
lfields = uv__get_internal_fields(loop);
|
||||
nevents = 0;
|
||||
|
||||
while (!QUEUE_EMPTY(&loop->watcher_queue)) {
|
||||
q = QUEUE_HEAD(&loop->watcher_queue);
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INIT(q);
|
||||
while (!uv__queue_empty(&loop->watcher_queue)) {
|
||||
q = uv__queue_head(&loop->watcher_queue);
|
||||
uv__queue_remove(q);
|
||||
uv__queue_init(q);
|
||||
|
||||
w = QUEUE_DATA(q, uv__io_t, watcher_queue);
|
||||
w = uv__queue_data(q, uv__io_t, watcher_queue);
|
||||
assert(w->pevents != 0);
|
||||
assert(w->fd >= 0);
|
||||
assert(w->fd < (int) loop->nwatchers);
|
||||
@ -307,8 +307,8 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
|
||||
|
||||
/* Handle kevent NOTE_EXIT results */
|
||||
if (ev->filter == EVFILT_PROC) {
|
||||
QUEUE_FOREACH(q, &loop->process_handles) {
|
||||
process = QUEUE_DATA(q, uv_process_t, queue);
|
||||
uv__queue_foreach(q, &loop->process_handles) {
|
||||
process = uv__queue_data(q, uv_process_t, queue);
|
||||
if (process->pid == fd) {
|
||||
process->flags |= UV_HANDLE_REAP;
|
||||
loop->flags |= UV_LOOP_REAP_CHILDREN;
|
||||
|
259
deps/libuv/src/unix/linux.c
vendored
259
deps/libuv/src/unix/linux.c
vendored
@ -48,6 +48,7 @@
|
||||
#include <sys/sysinfo.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@ -150,6 +151,11 @@ enum {
|
||||
UV__IORING_OP_CLOSE = 19,
|
||||
UV__IORING_OP_STATX = 21,
|
||||
UV__IORING_OP_EPOLL_CTL = 29,
|
||||
UV__IORING_OP_RENAMEAT = 35,
|
||||
UV__IORING_OP_UNLINKAT = 36,
|
||||
UV__IORING_OP_MKDIRAT = 37,
|
||||
UV__IORING_OP_SYMLINKAT = 38,
|
||||
UV__IORING_OP_LINKAT = 39,
|
||||
};
|
||||
|
||||
enum {
|
||||
@ -162,6 +168,10 @@ enum {
|
||||
UV__IORING_SQ_CQ_OVERFLOW = 2u,
|
||||
};
|
||||
|
||||
enum {
|
||||
UV__MKDIRAT_SYMLINKAT_LINKAT = 1u,
|
||||
};
|
||||
|
||||
struct uv__io_cqring_offsets {
|
||||
uint32_t head;
|
||||
uint32_t tail;
|
||||
@ -257,7 +267,7 @@ STATIC_ASSERT(EPOLL_CTL_MOD < 4);
|
||||
|
||||
struct watcher_list {
|
||||
RB_ENTRY(watcher_list) entry;
|
||||
QUEUE watchers;
|
||||
struct uv__queue watchers;
|
||||
int iterating;
|
||||
char* path;
|
||||
int wd;
|
||||
@ -300,6 +310,31 @@ static struct watcher_root* uv__inotify_watchers(uv_loop_t* loop) {
|
||||
}
|
||||
|
||||
|
||||
unsigned uv__kernel_version(void) {
|
||||
static _Atomic unsigned cached_version;
|
||||
struct utsname u;
|
||||
unsigned version;
|
||||
unsigned major;
|
||||
unsigned minor;
|
||||
unsigned patch;
|
||||
|
||||
version = atomic_load_explicit(&cached_version, memory_order_relaxed);
|
||||
if (version != 0)
|
||||
return version;
|
||||
|
||||
if (-1 == uname(&u))
|
||||
return 0;
|
||||
|
||||
if (3 != sscanf(u.release, "%u.%u.%u", &major, &minor, &patch))
|
||||
return 0;
|
||||
|
||||
version = major * 65536 + minor * 256 + patch;
|
||||
atomic_store_explicit(&cached_version, version, memory_order_relaxed);
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
ssize_t
|
||||
uv__fs_copy_file_range(int fd_in,
|
||||
off_t* off_in,
|
||||
@ -385,6 +420,9 @@ int uv__io_uring_register(int fd, unsigned opcode, void* arg, unsigned nargs) {
|
||||
|
||||
|
||||
static int uv__use_io_uring(void) {
|
||||
#if defined(__ANDROID_API__)
|
||||
return 0; /* Possibly available but blocked by seccomp. */
|
||||
#else
|
||||
/* Ternary: unknown=0, yes=1, no=-1 */
|
||||
static _Atomic int use_io_uring;
|
||||
char* val;
|
||||
@ -399,6 +437,7 @@ static int uv__use_io_uring(void) {
|
||||
}
|
||||
|
||||
return use > 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@ -503,6 +542,10 @@ static void uv__iou_init(int epollfd,
|
||||
iou->sqelen = sqelen;
|
||||
iou->ringfd = ringfd;
|
||||
iou->in_flight = 0;
|
||||
iou->flags = 0;
|
||||
|
||||
if (uv__kernel_version() >= /* 5.15.0 */ 0x050F00)
|
||||
iou->flags |= UV__MKDIRAT_SYMLINKAT_LINKAT;
|
||||
|
||||
for (i = 0; i <= iou->sqmask; i++)
|
||||
iou->sqarray[i] = i; /* Slot -> sqe identity mapping. */
|
||||
@ -684,7 +727,7 @@ static struct uv__io_uring_sqe* uv__iou_get_sqe(struct uv__iou* iou,
|
||||
req->work_req.loop = loop;
|
||||
req->work_req.work = NULL;
|
||||
req->work_req.done = NULL;
|
||||
QUEUE_INIT(&req->work_req.wq);
|
||||
uv__queue_init(&req->work_req.wq);
|
||||
|
||||
uv__req_register(loop, req);
|
||||
iou->in_flight++;
|
||||
@ -714,6 +757,17 @@ int uv__iou_fs_close(uv_loop_t* loop, uv_fs_t* req) {
|
||||
struct uv__io_uring_sqe* sqe;
|
||||
struct uv__iou* iou;
|
||||
|
||||
/* Work around a poorly understood bug in older kernels where closing a file
|
||||
* descriptor pointing to /foo/bar results in ETXTBSY errors when trying to
|
||||
* execve("/foo/bar") later on. The bug seems to have been fixed somewhere
|
||||
* between 5.15.85 and 5.15.90. I couldn't pinpoint the responsible commit
|
||||
* but good candidates are the several data race fixes. Interestingly, it
|
||||
* seems to manifest only when running under Docker so the possibility of
|
||||
* a Docker bug can't be completely ruled out either. Yay, computers.
|
||||
*/
|
||||
if (uv__kernel_version() < /* 5.15.90 */ 0x050F5A)
|
||||
return 0;
|
||||
|
||||
iou = &uv__get_internal_fields(loop)->iou;
|
||||
|
||||
sqe = uv__iou_get_sqe(iou, loop, req);
|
||||
@ -754,6 +808,55 @@ int uv__iou_fs_fsync_or_fdatasync(uv_loop_t* loop,
|
||||
}
|
||||
|
||||
|
||||
int uv__iou_fs_link(uv_loop_t* loop, uv_fs_t* req) {
|
||||
struct uv__io_uring_sqe* sqe;
|
||||
struct uv__iou* iou;
|
||||
|
||||
iou = &uv__get_internal_fields(loop)->iou;
|
||||
|
||||
if (!(iou->flags & UV__MKDIRAT_SYMLINKAT_LINKAT))
|
||||
return 0;
|
||||
|
||||
sqe = uv__iou_get_sqe(iou, loop, req);
|
||||
if (sqe == NULL)
|
||||
return 0;
|
||||
|
||||
sqe->addr = (uintptr_t) req->path;
|
||||
sqe->fd = AT_FDCWD;
|
||||
sqe->addr2 = (uintptr_t) req->new_path;
|
||||
sqe->len = AT_FDCWD;
|
||||
sqe->opcode = UV__IORING_OP_LINKAT;
|
||||
|
||||
uv__iou_submit(iou);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int uv__iou_fs_mkdir(uv_loop_t* loop, uv_fs_t* req) {
|
||||
struct uv__io_uring_sqe* sqe;
|
||||
struct uv__iou* iou;
|
||||
|
||||
iou = &uv__get_internal_fields(loop)->iou;
|
||||
|
||||
if (!(iou->flags & UV__MKDIRAT_SYMLINKAT_LINKAT))
|
||||
return 0;
|
||||
|
||||
sqe = uv__iou_get_sqe(iou, loop, req);
|
||||
if (sqe == NULL)
|
||||
return 0;
|
||||
|
||||
sqe->addr = (uintptr_t) req->path;
|
||||
sqe->fd = AT_FDCWD;
|
||||
sqe->len = req->mode;
|
||||
sqe->opcode = UV__IORING_OP_MKDIRAT;
|
||||
|
||||
uv__iou_submit(iou);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int uv__iou_fs_open(uv_loop_t* loop, uv_fs_t* req) {
|
||||
struct uv__io_uring_sqe* sqe;
|
||||
struct uv__iou* iou;
|
||||
@ -776,16 +879,86 @@ int uv__iou_fs_open(uv_loop_t* loop, uv_fs_t* req) {
|
||||
}
|
||||
|
||||
|
||||
int uv__iou_fs_rename(uv_loop_t* loop, uv_fs_t* req) {
|
||||
struct uv__io_uring_sqe* sqe;
|
||||
struct uv__iou* iou;
|
||||
|
||||
iou = &uv__get_internal_fields(loop)->iou;
|
||||
|
||||
sqe = uv__iou_get_sqe(iou, loop, req);
|
||||
if (sqe == NULL)
|
||||
return 0;
|
||||
|
||||
sqe->addr = (uintptr_t) req->path;
|
||||
sqe->fd = AT_FDCWD;
|
||||
sqe->addr2 = (uintptr_t) req->new_path;
|
||||
sqe->len = AT_FDCWD;
|
||||
sqe->opcode = UV__IORING_OP_RENAMEAT;
|
||||
|
||||
uv__iou_submit(iou);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int uv__iou_fs_symlink(uv_loop_t* loop, uv_fs_t* req) {
|
||||
struct uv__io_uring_sqe* sqe;
|
||||
struct uv__iou* iou;
|
||||
|
||||
iou = &uv__get_internal_fields(loop)->iou;
|
||||
|
||||
if (!(iou->flags & UV__MKDIRAT_SYMLINKAT_LINKAT))
|
||||
return 0;
|
||||
|
||||
sqe = uv__iou_get_sqe(iou, loop, req);
|
||||
if (sqe == NULL)
|
||||
return 0;
|
||||
|
||||
sqe->addr = (uintptr_t) req->path;
|
||||
sqe->fd = AT_FDCWD;
|
||||
sqe->addr2 = (uintptr_t) req->new_path;
|
||||
sqe->opcode = UV__IORING_OP_SYMLINKAT;
|
||||
|
||||
uv__iou_submit(iou);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int uv__iou_fs_unlink(uv_loop_t* loop, uv_fs_t* req) {
|
||||
struct uv__io_uring_sqe* sqe;
|
||||
struct uv__iou* iou;
|
||||
|
||||
iou = &uv__get_internal_fields(loop)->iou;
|
||||
|
||||
sqe = uv__iou_get_sqe(iou, loop, req);
|
||||
if (sqe == NULL)
|
||||
return 0;
|
||||
|
||||
sqe->addr = (uintptr_t) req->path;
|
||||
sqe->fd = AT_FDCWD;
|
||||
sqe->opcode = UV__IORING_OP_UNLINKAT;
|
||||
|
||||
uv__iou_submit(iou);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int uv__iou_fs_read_or_write(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
int is_read) {
|
||||
struct uv__io_uring_sqe* sqe;
|
||||
struct uv__iou* iou;
|
||||
|
||||
/* For the moment, if iovcnt is greater than IOV_MAX, fallback to the
|
||||
* threadpool. In the future we might take advantage of IOSQE_IO_LINK. */
|
||||
if (req->nbufs > IOV_MAX)
|
||||
return 0;
|
||||
/* If iovcnt is greater than IOV_MAX, cap it to IOV_MAX on reads and fallback
|
||||
* to the threadpool on writes */
|
||||
if (req->nbufs > IOV_MAX) {
|
||||
if (is_read)
|
||||
req->nbufs = IOV_MAX;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
iou = &uv__get_internal_fields(loop)->iou;
|
||||
|
||||
@ -1092,7 +1265,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
|
||||
struct uv__iou* ctl;
|
||||
struct uv__iou* iou;
|
||||
int real_timeout;
|
||||
QUEUE* q;
|
||||
struct uv__queue* q;
|
||||
uv__io_t* w;
|
||||
sigset_t* sigmask;
|
||||
sigset_t sigset;
|
||||
@ -1138,11 +1311,11 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
|
||||
|
||||
memset(&e, 0, sizeof(e));
|
||||
|
||||
while (!QUEUE_EMPTY(&loop->watcher_queue)) {
|
||||
q = QUEUE_HEAD(&loop->watcher_queue);
|
||||
w = QUEUE_DATA(q, uv__io_t, watcher_queue);
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INIT(q);
|
||||
while (!uv__queue_empty(&loop->watcher_queue)) {
|
||||
q = uv__queue_head(&loop->watcher_queue);
|
||||
w = uv__queue_data(q, uv__io_t, watcher_queue);
|
||||
uv__queue_remove(q);
|
||||
uv__queue_init(q);
|
||||
|
||||
op = EPOLL_CTL_MOD;
|
||||
if (w->events == 0)
|
||||
@ -1479,6 +1652,8 @@ int uv_cpu_info(uv_cpu_info_t** ci, int* count) {
|
||||
static const char model_marker[] = "CPU part\t: ";
|
||||
#elif defined(__mips__)
|
||||
static const char model_marker[] = "cpu model\t\t: ";
|
||||
#elif defined(__loongarch__)
|
||||
static const char model_marker[] = "cpu family\t\t: ";
|
||||
#else
|
||||
static const char model_marker[] = "model name\t: ";
|
||||
#endif
|
||||
@ -2097,8 +2272,8 @@ static int uv__inotify_fork(uv_loop_t* loop, struct watcher_list* root) {
|
||||
struct watcher_list* tmp_watcher_list_iter;
|
||||
struct watcher_list* watcher_list;
|
||||
struct watcher_list tmp_watcher_list;
|
||||
QUEUE queue;
|
||||
QUEUE* q;
|
||||
struct uv__queue queue;
|
||||
struct uv__queue* q;
|
||||
uv_fs_event_t* handle;
|
||||
char* tmp_path;
|
||||
|
||||
@ -2110,41 +2285,41 @@ static int uv__inotify_fork(uv_loop_t* loop, struct watcher_list* root) {
|
||||
*/
|
||||
loop->inotify_watchers = root;
|
||||
|
||||
QUEUE_INIT(&tmp_watcher_list.watchers);
|
||||
uv__queue_init(&tmp_watcher_list.watchers);
|
||||
/* Note that the queue we use is shared with the start and stop()
|
||||
* functions, making QUEUE_FOREACH unsafe to use. So we use the
|
||||
* QUEUE_MOVE trick to safely iterate. Also don't free the watcher
|
||||
* functions, making uv__queue_foreach unsafe to use. So we use the
|
||||
* uv__queue_move trick to safely iterate. Also don't free the watcher
|
||||
* list until we're done iterating. c.f. uv__inotify_read.
|
||||
*/
|
||||
RB_FOREACH_SAFE(watcher_list, watcher_root,
|
||||
uv__inotify_watchers(loop), tmp_watcher_list_iter) {
|
||||
watcher_list->iterating = 1;
|
||||
QUEUE_MOVE(&watcher_list->watchers, &queue);
|
||||
while (!QUEUE_EMPTY(&queue)) {
|
||||
q = QUEUE_HEAD(&queue);
|
||||
handle = QUEUE_DATA(q, uv_fs_event_t, watchers);
|
||||
uv__queue_move(&watcher_list->watchers, &queue);
|
||||
while (!uv__queue_empty(&queue)) {
|
||||
q = uv__queue_head(&queue);
|
||||
handle = uv__queue_data(q, uv_fs_event_t, watchers);
|
||||
/* It's critical to keep a copy of path here, because it
|
||||
* will be set to NULL by stop() and then deallocated by
|
||||
* maybe_free_watcher_list
|
||||
*/
|
||||
tmp_path = uv__strdup(handle->path);
|
||||
assert(tmp_path != NULL);
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INSERT_TAIL(&watcher_list->watchers, q);
|
||||
uv__queue_remove(q);
|
||||
uv__queue_insert_tail(&watcher_list->watchers, q);
|
||||
uv_fs_event_stop(handle);
|
||||
|
||||
QUEUE_INSERT_TAIL(&tmp_watcher_list.watchers, &handle->watchers);
|
||||
uv__queue_insert_tail(&tmp_watcher_list.watchers, &handle->watchers);
|
||||
handle->path = tmp_path;
|
||||
}
|
||||
watcher_list->iterating = 0;
|
||||
maybe_free_watcher_list(watcher_list, loop);
|
||||
}
|
||||
|
||||
QUEUE_MOVE(&tmp_watcher_list.watchers, &queue);
|
||||
while (!QUEUE_EMPTY(&queue)) {
|
||||
q = QUEUE_HEAD(&queue);
|
||||
QUEUE_REMOVE(q);
|
||||
handle = QUEUE_DATA(q, uv_fs_event_t, watchers);
|
||||
uv__queue_move(&tmp_watcher_list.watchers, &queue);
|
||||
while (!uv__queue_empty(&queue)) {
|
||||
q = uv__queue_head(&queue);
|
||||
uv__queue_remove(q);
|
||||
handle = uv__queue_data(q, uv_fs_event_t, watchers);
|
||||
tmp_path = handle->path;
|
||||
handle->path = NULL;
|
||||
err = uv_fs_event_start(handle, handle->cb, tmp_path, 0);
|
||||
@ -2166,7 +2341,7 @@ static struct watcher_list* find_watcher(uv_loop_t* loop, int wd) {
|
||||
|
||||
static void maybe_free_watcher_list(struct watcher_list* w, uv_loop_t* loop) {
|
||||
/* if the watcher_list->watchers is being iterated over, we can't free it. */
|
||||
if ((!w->iterating) && QUEUE_EMPTY(&w->watchers)) {
|
||||
if ((!w->iterating) && uv__queue_empty(&w->watchers)) {
|
||||
/* No watchers left for this path. Clean up. */
|
||||
RB_REMOVE(watcher_root, uv__inotify_watchers(loop), w);
|
||||
inotify_rm_watch(loop->inotify_fd, w->wd);
|
||||
@ -2181,8 +2356,8 @@ static void uv__inotify_read(uv_loop_t* loop,
|
||||
const struct inotify_event* e;
|
||||
struct watcher_list* w;
|
||||
uv_fs_event_t* h;
|
||||
QUEUE queue;
|
||||
QUEUE* q;
|
||||
struct uv__queue queue;
|
||||
struct uv__queue* q;
|
||||
const char* path;
|
||||
ssize_t size;
|
||||
const char *p;
|
||||
@ -2225,7 +2400,7 @@ static void uv__inotify_read(uv_loop_t* loop,
|
||||
* What can go wrong?
|
||||
* A callback could call uv_fs_event_stop()
|
||||
* and the queue can change under our feet.
|
||||
* So, we use QUEUE_MOVE() trick to safely iterate over the queue.
|
||||
* So, we use uv__queue_move() trick to safely iterate over the queue.
|
||||
* And we don't free the watcher_list until we're done iterating.
|
||||
*
|
||||
* First,
|
||||
@ -2233,13 +2408,13 @@ static void uv__inotify_read(uv_loop_t* loop,
|
||||
* not to free watcher_list.
|
||||
*/
|
||||
w->iterating = 1;
|
||||
QUEUE_MOVE(&w->watchers, &queue);
|
||||
while (!QUEUE_EMPTY(&queue)) {
|
||||
q = QUEUE_HEAD(&queue);
|
||||
h = QUEUE_DATA(q, uv_fs_event_t, watchers);
|
||||
uv__queue_move(&w->watchers, &queue);
|
||||
while (!uv__queue_empty(&queue)) {
|
||||
q = uv__queue_head(&queue);
|
||||
h = uv__queue_data(q, uv_fs_event_t, watchers);
|
||||
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INSERT_TAIL(&w->watchers, q);
|
||||
uv__queue_remove(q);
|
||||
uv__queue_insert_tail(&w->watchers, q);
|
||||
|
||||
h->cb(h, path, events, 0);
|
||||
}
|
||||
@ -2301,13 +2476,13 @@ int uv_fs_event_start(uv_fs_event_t* handle,
|
||||
|
||||
w->wd = wd;
|
||||
w->path = memcpy(w + 1, path, len);
|
||||
QUEUE_INIT(&w->watchers);
|
||||
uv__queue_init(&w->watchers);
|
||||
w->iterating = 0;
|
||||
RB_INSERT(watcher_root, uv__inotify_watchers(loop), w);
|
||||
|
||||
no_insert:
|
||||
uv__handle_start(handle);
|
||||
QUEUE_INSERT_TAIL(&w->watchers, &handle->watchers);
|
||||
uv__queue_insert_tail(&w->watchers, &handle->watchers);
|
||||
handle->path = w->path;
|
||||
handle->cb = cb;
|
||||
handle->wd = wd;
|
||||
@ -2328,7 +2503,7 @@ int uv_fs_event_stop(uv_fs_event_t* handle) {
|
||||
handle->wd = -1;
|
||||
handle->path = NULL;
|
||||
uv__handle_stop(handle);
|
||||
QUEUE_REMOVE(&handle->watchers);
|
||||
uv__queue_remove(&handle->watchers);
|
||||
|
||||
maybe_free_watcher_list(w, handle->loop);
|
||||
|
||||
|
20
deps/libuv/src/unix/loop-watcher.c
vendored
20
deps/libuv/src/unix/loop-watcher.c
vendored
@ -32,7 +32,7 @@
|
||||
int uv_##name##_start(uv_##name##_t* handle, uv_##name##_cb cb) { \
|
||||
if (uv__is_active(handle)) return 0; \
|
||||
if (cb == NULL) return UV_EINVAL; \
|
||||
QUEUE_INSERT_HEAD(&handle->loop->name##_handles, &handle->queue); \
|
||||
uv__queue_insert_head(&handle->loop->name##_handles, &handle->queue); \
|
||||
handle->name##_cb = cb; \
|
||||
uv__handle_start(handle); \
|
||||
return 0; \
|
||||
@ -40,21 +40,21 @@
|
||||
\
|
||||
int uv_##name##_stop(uv_##name##_t* handle) { \
|
||||
if (!uv__is_active(handle)) return 0; \
|
||||
QUEUE_REMOVE(&handle->queue); \
|
||||
uv__queue_remove(&handle->queue); \
|
||||
uv__handle_stop(handle); \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
void uv__run_##name(uv_loop_t* loop) { \
|
||||
uv_##name##_t* h; \
|
||||
QUEUE queue; \
|
||||
QUEUE* q; \
|
||||
QUEUE_MOVE(&loop->name##_handles, &queue); \
|
||||
while (!QUEUE_EMPTY(&queue)) { \
|
||||
q = QUEUE_HEAD(&queue); \
|
||||
h = QUEUE_DATA(q, uv_##name##_t, queue); \
|
||||
QUEUE_REMOVE(q); \
|
||||
QUEUE_INSERT_TAIL(&loop->name##_handles, q); \
|
||||
struct uv__queue queue; \
|
||||
struct uv__queue* q; \
|
||||
uv__queue_move(&loop->name##_handles, &queue); \
|
||||
while (!uv__queue_empty(&queue)) { \
|
||||
q = uv__queue_head(&queue); \
|
||||
h = uv__queue_data(q, uv_##name##_t, queue); \
|
||||
uv__queue_remove(q); \
|
||||
uv__queue_insert_tail(&loop->name##_handles, q); \
|
||||
h->name##_cb(h); \
|
||||
} \
|
||||
} \
|
||||
|
28
deps/libuv/src/unix/loop.c
vendored
28
deps/libuv/src/unix/loop.c
vendored
@ -50,20 +50,20 @@ int uv_loop_init(uv_loop_t* loop) {
|
||||
sizeof(lfields->loop_metrics.metrics));
|
||||
|
||||
heap_init((struct heap*) &loop->timer_heap);
|
||||
QUEUE_INIT(&loop->wq);
|
||||
QUEUE_INIT(&loop->idle_handles);
|
||||
QUEUE_INIT(&loop->async_handles);
|
||||
QUEUE_INIT(&loop->check_handles);
|
||||
QUEUE_INIT(&loop->prepare_handles);
|
||||
QUEUE_INIT(&loop->handle_queue);
|
||||
uv__queue_init(&loop->wq);
|
||||
uv__queue_init(&loop->idle_handles);
|
||||
uv__queue_init(&loop->async_handles);
|
||||
uv__queue_init(&loop->check_handles);
|
||||
uv__queue_init(&loop->prepare_handles);
|
||||
uv__queue_init(&loop->handle_queue);
|
||||
|
||||
loop->active_handles = 0;
|
||||
loop->active_reqs.count = 0;
|
||||
loop->nfds = 0;
|
||||
loop->watchers = NULL;
|
||||
loop->nwatchers = 0;
|
||||
QUEUE_INIT(&loop->pending_queue);
|
||||
QUEUE_INIT(&loop->watcher_queue);
|
||||
uv__queue_init(&loop->pending_queue);
|
||||
uv__queue_init(&loop->watcher_queue);
|
||||
|
||||
loop->closing_handles = NULL;
|
||||
uv__update_time(loop);
|
||||
@ -85,7 +85,7 @@ int uv_loop_init(uv_loop_t* loop) {
|
||||
err = uv__process_init(loop);
|
||||
if (err)
|
||||
goto fail_signal_init;
|
||||
QUEUE_INIT(&loop->process_handles);
|
||||
uv__queue_init(&loop->process_handles);
|
||||
|
||||
err = uv_rwlock_init(&loop->cloexec_lock);
|
||||
if (err)
|
||||
@ -152,9 +152,9 @@ int uv_loop_fork(uv_loop_t* loop) {
|
||||
if (w == NULL)
|
||||
continue;
|
||||
|
||||
if (w->pevents != 0 && QUEUE_EMPTY(&w->watcher_queue)) {
|
||||
if (w->pevents != 0 && uv__queue_empty(&w->watcher_queue)) {
|
||||
w->events = 0; /* Force re-registration in uv__io_poll. */
|
||||
QUEUE_INSERT_TAIL(&loop->watcher_queue, &w->watcher_queue);
|
||||
uv__queue_insert_tail(&loop->watcher_queue, &w->watcher_queue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@ void uv__loop_close(uv_loop_t* loop) {
|
||||
}
|
||||
|
||||
uv_mutex_lock(&loop->wq_mutex);
|
||||
assert(QUEUE_EMPTY(&loop->wq) && "thread pool work queue not empty!");
|
||||
assert(uv__queue_empty(&loop->wq) && "thread pool work queue not empty!");
|
||||
assert(!uv__has_active_reqs(loop));
|
||||
uv_mutex_unlock(&loop->wq_mutex);
|
||||
uv_mutex_destroy(&loop->wq_mutex);
|
||||
@ -192,8 +192,8 @@ void uv__loop_close(uv_loop_t* loop) {
|
||||
uv_rwlock_destroy(&loop->cloexec_lock);
|
||||
|
||||
#if 0
|
||||
assert(QUEUE_EMPTY(&loop->pending_queue));
|
||||
assert(QUEUE_EMPTY(&loop->watcher_queue));
|
||||
assert(uv__queue_empty(&loop->pending_queue));
|
||||
assert(uv__queue_empty(&loop->watcher_queue));
|
||||
assert(loop->nfds == 0);
|
||||
#endif
|
||||
|
||||
|
24
deps/libuv/src/unix/os390-syscalls.c
vendored
24
deps/libuv/src/unix/os390-syscalls.c
vendored
@ -27,7 +27,7 @@
|
||||
#include <termios.h>
|
||||
#include <sys/msg.h>
|
||||
|
||||
static QUEUE global_epoll_queue;
|
||||
static struct uv__queue global_epoll_queue;
|
||||
static uv_mutex_t global_epoll_lock;
|
||||
static uv_once_t once = UV_ONCE_INIT;
|
||||
|
||||
@ -178,18 +178,18 @@ static void after_fork(void) {
|
||||
|
||||
|
||||
static void child_fork(void) {
|
||||
QUEUE* q;
|
||||
struct uv__queue* q;
|
||||
uv_once_t child_once = UV_ONCE_INIT;
|
||||
|
||||
/* reset once */
|
||||
memcpy(&once, &child_once, sizeof(child_once));
|
||||
|
||||
/* reset epoll list */
|
||||
while (!QUEUE_EMPTY(&global_epoll_queue)) {
|
||||
while (!uv__queue_empty(&global_epoll_queue)) {
|
||||
uv__os390_epoll* lst;
|
||||
q = QUEUE_HEAD(&global_epoll_queue);
|
||||
QUEUE_REMOVE(q);
|
||||
lst = QUEUE_DATA(q, uv__os390_epoll, member);
|
||||
q = uv__queue_head(&global_epoll_queue);
|
||||
uv__queue_remove(q);
|
||||
lst = uv__queue_data(q, uv__os390_epoll, member);
|
||||
uv__free(lst->items);
|
||||
lst->items = NULL;
|
||||
lst->size = 0;
|
||||
@ -201,7 +201,7 @@ static void child_fork(void) {
|
||||
|
||||
|
||||
static void epoll_init(void) {
|
||||
QUEUE_INIT(&global_epoll_queue);
|
||||
uv__queue_init(&global_epoll_queue);
|
||||
if (uv_mutex_init(&global_epoll_lock))
|
||||
abort();
|
||||
|
||||
@ -225,7 +225,7 @@ uv__os390_epoll* epoll_create1(int flags) {
|
||||
lst->items[lst->size - 1].revents = 0;
|
||||
uv_once(&once, epoll_init);
|
||||
uv_mutex_lock(&global_epoll_lock);
|
||||
QUEUE_INSERT_TAIL(&global_epoll_queue, &lst->member);
|
||||
uv__queue_insert_tail(&global_epoll_queue, &lst->member);
|
||||
uv_mutex_unlock(&global_epoll_lock);
|
||||
}
|
||||
|
||||
@ -352,14 +352,14 @@ int epoll_wait(uv__os390_epoll* lst, struct epoll_event* events,
|
||||
|
||||
|
||||
int epoll_file_close(int fd) {
|
||||
QUEUE* q;
|
||||
struct uv__queue* q;
|
||||
|
||||
uv_once(&once, epoll_init);
|
||||
uv_mutex_lock(&global_epoll_lock);
|
||||
QUEUE_FOREACH(q, &global_epoll_queue) {
|
||||
uv__queue_foreach(q, &global_epoll_queue) {
|
||||
uv__os390_epoll* lst;
|
||||
|
||||
lst = QUEUE_DATA(q, uv__os390_epoll, member);
|
||||
lst = uv__queue_data(q, uv__os390_epoll, member);
|
||||
if (fd < lst->size && lst->items != NULL && lst->items[fd].fd != -1)
|
||||
lst->items[fd].fd = -1;
|
||||
}
|
||||
@ -371,7 +371,7 @@ int epoll_file_close(int fd) {
|
||||
void epoll_queue_close(uv__os390_epoll* lst) {
|
||||
/* Remove epoll instance from global queue */
|
||||
uv_mutex_lock(&global_epoll_lock);
|
||||
QUEUE_REMOVE(&lst->member);
|
||||
uv__queue_remove(&lst->member);
|
||||
uv_mutex_unlock(&global_epoll_lock);
|
||||
|
||||
/* Free resources */
|
||||
|
2
deps/libuv/src/unix/os390-syscalls.h
vendored
2
deps/libuv/src/unix/os390-syscalls.h
vendored
@ -45,7 +45,7 @@ struct epoll_event {
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
QUEUE member;
|
||||
struct uv__queue member;
|
||||
struct pollfd* items;
|
||||
unsigned long size;
|
||||
int msg_queue;
|
||||
|
14
deps/libuv/src/unix/os390.c
vendored
14
deps/libuv/src/unix/os390.c
vendored
@ -815,7 +815,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
|
||||
uv__os390_epoll* ep;
|
||||
int have_signals;
|
||||
int real_timeout;
|
||||
QUEUE* q;
|
||||
struct uv__queue* q;
|
||||
uv__io_t* w;
|
||||
uint64_t base;
|
||||
int count;
|
||||
@ -827,19 +827,19 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
|
||||
int reset_timeout;
|
||||
|
||||
if (loop->nfds == 0) {
|
||||
assert(QUEUE_EMPTY(&loop->watcher_queue));
|
||||
assert(uv__queue_empty(&loop->watcher_queue));
|
||||
return;
|
||||
}
|
||||
|
||||
lfields = uv__get_internal_fields(loop);
|
||||
|
||||
while (!QUEUE_EMPTY(&loop->watcher_queue)) {
|
||||
while (!uv__queue_empty(&loop->watcher_queue)) {
|
||||
uv_stream_t* stream;
|
||||
|
||||
q = QUEUE_HEAD(&loop->watcher_queue);
|
||||
QUEUE_REMOVE(q);
|
||||
QUEUE_INIT(q);
|
||||
w = QUEUE_DATA(q, uv__io_t, watcher_queue);
|
||||
q = uv__queue_head(&loop->watcher_queue);
|
||||
uv__queue_remove(q);
|
||||
uv__queue_init(q);
|
||||
w = uv__queue_data(q, uv__io_t, watcher_queue);
|
||||
|
||||
assert(w->pevents != 0);
|
||||
assert(w->fd >= 0);
|
||||
|
98
deps/libuv/src/unix/pipe.c
vendored
98
deps/libuv/src/unix/pipe.c
vendored
@ -41,26 +41,60 @@ int uv_pipe_init(uv_loop_t* loop, uv_pipe_t* handle, int ipc) {
|
||||
|
||||
|
||||
int uv_pipe_bind(uv_pipe_t* handle, const char* name) {
|
||||
return uv_pipe_bind2(handle, name, strlen(name), 0);
|
||||
}
|
||||
|
||||
|
||||
int uv_pipe_bind2(uv_pipe_t* handle,
|
||||
const char* name,
|
||||
size_t namelen,
|
||||
unsigned int flags) {
|
||||
struct sockaddr_un saddr;
|
||||
const char* pipe_fname;
|
||||
char* pipe_fname;
|
||||
int sockfd;
|
||||
int err;
|
||||
|
||||
pipe_fname = NULL;
|
||||
|
||||
if (flags & ~UV_PIPE_NO_TRUNCATE)
|
||||
return UV_EINVAL;
|
||||
|
||||
if (name == NULL)
|
||||
return UV_EINVAL;
|
||||
|
||||
if (namelen == 0)
|
||||
return UV_EINVAL;
|
||||
|
||||
#ifndef __linux__
|
||||
/* Abstract socket namespace only works on Linux. */
|
||||
if (*name == '\0')
|
||||
return UV_EINVAL;
|
||||
#endif
|
||||
|
||||
if (flags & UV_PIPE_NO_TRUNCATE)
|
||||
if (namelen > sizeof(saddr.sun_path))
|
||||
return UV_EINVAL;
|
||||
|
||||
/* Truncate long paths. Documented behavior. */
|
||||
if (namelen > sizeof(saddr.sun_path))
|
||||
namelen = sizeof(saddr.sun_path);
|
||||
|
||||
/* Already bound? */
|
||||
if (uv__stream_fd(handle) >= 0)
|
||||
return UV_EINVAL;
|
||||
if (uv__is_closing(handle)) {
|
||||
return UV_EINVAL;
|
||||
}
|
||||
/* Make a copy of the file name, it outlives this function's scope. */
|
||||
pipe_fname = uv__strdup(name);
|
||||
if (pipe_fname == NULL)
|
||||
return UV_ENOMEM;
|
||||
|
||||
/* We've got a copy, don't touch the original any more. */
|
||||
name = NULL;
|
||||
if (uv__is_closing(handle))
|
||||
return UV_EINVAL;
|
||||
|
||||
/* Make a copy of the file path unless it is an abstract socket.
|
||||
* We unlink the file later but abstract sockets disappear
|
||||
* automatically since they're not real file system entities.
|
||||
*/
|
||||
if (*name != '\0') {
|
||||
pipe_fname = uv__strdup(name);
|
||||
if (pipe_fname == NULL)
|
||||
return UV_ENOMEM;
|
||||
}
|
||||
|
||||
err = uv__socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (err < 0)
|
||||
@ -68,7 +102,7 @@ int uv_pipe_bind(uv_pipe_t* handle, const char* name) {
|
||||
sockfd = err;
|
||||
|
||||
memset(&saddr, 0, sizeof saddr);
|
||||
uv__strscpy(saddr.sun_path, pipe_fname, sizeof(saddr.sun_path));
|
||||
memcpy(&saddr.sun_path, name, namelen);
|
||||
saddr.sun_family = AF_UNIX;
|
||||
|
||||
if (bind(sockfd, (struct sockaddr*)&saddr, sizeof saddr)) {
|
||||
@ -83,12 +117,12 @@ int uv_pipe_bind(uv_pipe_t* handle, const char* name) {
|
||||
|
||||
/* Success. */
|
||||
handle->flags |= UV_HANDLE_BOUND;
|
||||
handle->pipe_fname = pipe_fname; /* Is a strdup'ed copy. */
|
||||
handle->pipe_fname = pipe_fname; /* NULL or a strdup'ed copy. */
|
||||
handle->io_watcher.fd = sockfd;
|
||||
return 0;
|
||||
|
||||
err_socket:
|
||||
uv__free((void*)pipe_fname);
|
||||
uv__free(pipe_fname);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -176,11 +210,44 @@ void uv_pipe_connect(uv_connect_t* req,
|
||||
uv_pipe_t* handle,
|
||||
const char* name,
|
||||
uv_connect_cb cb) {
|
||||
uv_pipe_connect2(req, handle, name, strlen(name), 0, cb);
|
||||
}
|
||||
|
||||
|
||||
int uv_pipe_connect2(uv_connect_t* req,
|
||||
uv_pipe_t* handle,
|
||||
const char* name,
|
||||
size_t namelen,
|
||||
unsigned int flags,
|
||||
uv_connect_cb cb) {
|
||||
struct sockaddr_un saddr;
|
||||
int new_sock;
|
||||
int err;
|
||||
int r;
|
||||
|
||||
if (flags & ~UV_PIPE_NO_TRUNCATE)
|
||||
return UV_EINVAL;
|
||||
|
||||
if (name == NULL)
|
||||
return UV_EINVAL;
|
||||
|
||||
if (namelen == 0)
|
||||
return UV_EINVAL;
|
||||
|
||||
#ifndef __linux__
|
||||
/* Abstract socket namespace only works on Linux. */
|
||||
if (*name == '\0')
|
||||
return UV_EINVAL;
|
||||
#endif
|
||||
|
||||
if (flags & UV_PIPE_NO_TRUNCATE)
|
||||
if (namelen > sizeof(saddr.sun_path))
|
||||
return UV_EINVAL;
|
||||
|
||||
/* Truncate long paths. Documented behavior. */
|
||||
if (namelen > sizeof(saddr.sun_path))
|
||||
namelen = sizeof(saddr.sun_path);
|
||||
|
||||
new_sock = (uv__stream_fd(handle) == -1);
|
||||
|
||||
if (new_sock) {
|
||||
@ -191,7 +258,7 @@ void uv_pipe_connect(uv_connect_t* req,
|
||||
}
|
||||
|
||||
memset(&saddr, 0, sizeof saddr);
|
||||
uv__strscpy(saddr.sun_path, name, sizeof(saddr.sun_path));
|
||||
memcpy(&saddr.sun_path, name, namelen);
|
||||
saddr.sun_family = AF_UNIX;
|
||||
|
||||
do {
|
||||
@ -230,12 +297,13 @@ out:
|
||||
uv__req_init(handle->loop, req, UV_CONNECT);
|
||||
req->handle = (uv_stream_t*)handle;
|
||||
req->cb = cb;
|
||||
QUEUE_INIT(&req->queue);
|
||||
uv__queue_init(&req->queue);
|
||||
|
||||
/* Force callback to run on next tick in case of error. */
|
||||
if (err)
|
||||
uv__io_feed(handle->loop, &handle->io_watcher);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user