diff --git a/src/packages/jupyter/kernel/kernel.ts b/src/packages/jupyter/kernel/kernel.ts
index 48f380a224..7d8a452c78 100644
--- a/src/packages/jupyter/kernel/kernel.ts
+++ b/src/packages/jupyter/kernel/kernel.ts
@@ -152,6 +152,13 @@ export async function initJupyterRedux(syncdb: SyncDB, client: Client) {
);
}
+export async function getJupyterRedux(syncdb: SyncDB) {
+ const project_id = syncdb.project_id;
+ const path = original_path(syncdb.get_path());
+ const name = redux_name(project_id, path);
+ return { actions: redux.getActions(name), store: redux.getStore(name) };
+}
+
// Remove the store/actions for a given Jupyter notebook,
// and also close the kernel if it is running.
export async function removeJupyterRedux(
diff --git a/src/packages/next/components/landing/cocalc-com-features.tsx b/src/packages/next/components/landing/cocalc-com-features.tsx
index a4cab26136..899ab43aca 100644
--- a/src/packages/next/components/landing/cocalc-com-features.tsx
+++ b/src/packages/next/components/landing/cocalc-com-features.tsx
@@ -59,9 +59,8 @@ export function CoCalcComFeatures() {
belowWide={true}
>
- With {siteName}, you can easily collaborate with colleagues,
- students, and friends to edit computational documents. We support
- {" "}
+ With {siteName}, you can easily collaborate with colleagues, students,
+ and friends to edit computational documents. We support{" "}
Jupyter Notebooks
@@ -72,18 +71,16 @@ export function CoCalcComFeatures() {
- Everyone's code runs in the same per-project environment, which provides
- consistent results, synchronized file changes, and automatic revision
- history so that you can go back in time when you need to discover what
- changed and when. {shareServer && renderShareServer()}
+ Everyone's code runs in the same per-project environment, which
+ provides consistent results, synchronized file changes, and automatic
+ revision history so that you can go back in time when you need to
+ discover what changed and when. {renderShareServer()}
Forget the frustration of sending files back and forth between your
- collaborators, wasting time reviewing changes, and merging documents. {" "}
-
- Get started with {siteName} today.
-
+ collaborators, wasting time reviewing changes, and merging documents.{" "}
+ Get started with {siteName} today.
);
@@ -162,12 +159,9 @@ export function CoCalcComFeatures() {
return (
<>
- { " " }You can even publish your { siteName } creations to share with
- anyone via the built-in { " " }
-
- share server
-
- .
+ {" "}
+ You can even publish your {siteName} creations to share with anyone via
+ the built-in share server.
>
);
}
diff --git a/src/packages/project/browser-websocket/api.ts b/src/packages/project/browser-websocket/api.ts
index de775bee95..1fbcb87392 100644
--- a/src/packages/project/browser-websocket/api.ts
+++ b/src/packages/project/browser-websocket/api.ts
@@ -134,11 +134,6 @@ async function handleApiCall(data: Mesg, spark): Promise {
case "prettier_string": // deprecated
case "formatter_string":
return await run_formatter_string(data.path, data.str, data.options, log);
- case "jupyter":
- // DEPRECATED: The "jupyter" endpoint is only here for browser client
- // backward compatibility. Can be safely deleted soon, but not immediately
- // to make the release easier
- return await jupyter(data.path, data.endpoint, data.query);
case "exec":
if (data.opts?.compute_server_id) {
if (data.opts.filesystem) {
@@ -222,8 +217,6 @@ async function listing(
}
}
-import { handleApiRequest as jupyter } from "@cocalc/jupyter/kernel/websocket-api";
-
// Execute code
import { executeCode } from "@cocalc/backend/execute-code";
diff --git a/src/packages/project/sync/server.ts b/src/packages/project/sync/server.ts
index 8eca3a7f75..2ade893ba3 100644
--- a/src/packages/project/sync/server.ts
+++ b/src/packages/project/sync/server.ts
@@ -21,11 +21,6 @@ silently swallowed in persistent mode...
// and https://github.com/sagemathinc/cocalc/issues/5823
// and https://github.com/sagemathinc/cocalc/issues/5617
-// Setting this to 0 to optimize resource usage and because opening files
-// is fast, and also on the current tab gets opened on refresh anyways.
-
-const CLOSE_DELAY_MS = 0;
-
// This is a hard upper bound on the number of browser sessions that could
// have the same file open at once. We put some limit on it, to at least
// limit problems from bugs which crash projects (since each connection uses
@@ -56,7 +51,7 @@ import {
// @ts-ignore -- typescript nonsense.
const _ = set_debug;
-import { init_syncdoc } from "./sync-doc";
+import { init_syncdoc, getSyncDocFromSyncTable } from "./sync-doc";
import { key, register_synctable } from "./open-synctables";
import { reuseInFlight } from "@cocalc/util/reuse-in-flight";
import { once } from "@cocalc/util/async-utils";
@@ -68,6 +63,7 @@ import { register_project_status_table } from "./project-status";
import { register_usage_info_table } from "./usage-info";
import type { MergeType } from "@cocalc/sync/table/synctable";
import Client from "@cocalc/sync-client";
+import { getJupyterRedux } from "@cocalc/jupyter/kernel";
type Query = { [key: string]: any };
@@ -373,7 +369,7 @@ class SyncTableChannel {
}
}
- this.check_if_should_close();
+ this.check_if_should_save_or_close();
}
private send_synctable_to_browser(spark: Spark): void {
@@ -389,18 +385,20 @@ class SyncTableChannel {
this.channel.write(x);
}
- /* Check if we should close, e.g., due to no connected clients. */
- private check_if_should_close(): void {
- if (this.closed || this.persistent) {
- // don't bother if either already closed, or the persistent option is set.
+ /* This is called when a user disconnects. This always triggers a save to
+ disk. It may also trigger closing the file in some cases. */
+ private async check_if_should_save_or_close() {
+ if (this.closed) {
+ // don't bother if either already closed
return;
}
- const { n } = this.num_connections;
- if (n === 0) {
- this.log("check_if_should_close -- ", n, " -- do a save and maybe close");
- this.save_and_close_if_possible();
- } else {
- this.log("check_if_should_close -- ", n, " -- do not close");
+ this.log("check_if_should_save_or_close: save to disk if possible");
+ await this.save_if_possible();
+ const { n } = this.num_connections ?? {};
+ this.log("check_if_should_save_or_close", { n });
+ if (!this.persistent && n === 0) {
+ this.log("check_if_should_save_or_close: close if possible");
+ await this.close_if_possible();
}
}
@@ -450,33 +448,57 @@ class SyncTableChannel {
this.channel.write(x);
}
- private async save_and_close_if_possible(): Promise {
+ private async save_if_possible(): Promise {
if (this.closed || this.closing) {
return; // closing or already closed
}
- this.log("save_and_close_if_possible: no connections, so saving...");
+ this.log("save_if_possible: saves changes to database");
await this.synctable.save();
+ if (this.synctable.table === "syncstrings") {
+ this.log("save_if_possible: also fetch syncdoc");
+ const syncdoc = getSyncDocFromSyncTable(this.synctable);
+ if (syncdoc != null) {
+ const path = syncdoc.get_path();
+ this.log("save_if_possible: saving syncdoc to disk", { path });
+ if (path.endsWith(".sage-jupyter2")) {
+ // treat jupyter notebooks in a special way, since they have
+ // an aux .ipynb file that the syncdoc doesn't know about. In
+ // this case we save the ipynb to disk, not just the hidden
+ // syncdb file.
+ const { actions } = await getJupyterRedux(syncdoc);
+ if (actions == null) {
+ this.log("save_if_possible: jupyter -- actions is null");
+ } else {
+ if (!actions.isCellRunner()) {
+ this.log("save_if_possible: jupyter -- not cell runner");
+ return;
+ }
+ this.log("save_if_possible: jupyter -- saving to ipynb");
+ await actions.save_ipynb_file();
+ }
+ }
+ await syncdoc.save_to_disk();
+ } else {
+ this.log("save_if_possible: no syncdoc");
+ }
+ }
+ }
+
+ private async close_if_possible(): Promise {
+ if (this.closed || this.closing) {
+ return; // closing or already closed
+ }
const { n, changed } = this.num_connections;
const delay = Date.now() - changed.valueOf();
this.log(
- `save_and_close_if_possible: after save there are ${n} connections and delay=${delay}`,
+ `close_if_possible: there are ${n} connections and delay=${delay}`,
);
if (n === 0) {
- if (delay < CLOSE_DELAY_MS) {
- this.log(`save_and_close_if_possible: wait a bit then try again`);
- setTimeout(
- this.check_if_should_close.bind(this),
- 1000 + CLOSE_DELAY_MS - delay,
- );
- } else {
- this.log(
- `save_and_close_if_possible: close this SyncTableChannel atomically`,
- );
- // actually close
- this.close();
- }
+ this.log(`close_if_possible: close this SyncTableChannel atomically`);
+ // actually close
+ this.close();
} else {
- this.log(`save_and_close_if_possible: NOT closing this SyncTableChannel`);
+ this.log(`close_if_possible: NOT closing this SyncTableChannel`);
}
}
diff --git a/src/packages/project/sync/sync-doc.ts b/src/packages/project/sync/sync-doc.ts
index 7145abb353..0bc510bfe5 100644
--- a/src/packages/project/sync/sync-doc.ts
+++ b/src/packages/project/sync/sync-doc.ts
@@ -163,6 +163,11 @@ export function get_syncdoc(path: string): SyncDoc | undefined {
return syncDocs.get(path);
}
+export function getSyncDocFromSyncTable(synctable: SyncTable) {
+ const { opts } = get_type_and_opts(synctable);
+ return get_syncdoc(opts.path);
+}
+
async function init_syncdoc_async(
client: Client,
synctable: SyncTable,
diff --git a/src/packages/server/salesloft/money.ts b/src/packages/server/salesloft/money.ts
index 7cd6f660c6..76c4ba8247 100644
--- a/src/packages/server/salesloft/money.ts
+++ b/src/packages/server/salesloft/money.ts
@@ -59,7 +59,7 @@ export async function updateMoney(cutoff: string = "2 days") {
}
const data = await getMoneyData(account_id);
log.debug("updateMoney: ", { salesloft_id: id, account_id, data });
- await update(id, data);
+ await update(id, { custom_fields: data });
}
}
diff --git a/src/packages/util/smc-version.js b/src/packages/util/smc-version.js
index b19e97d8bb..79c35cf353 100644
--- a/src/packages/util/smc-version.js
+++ b/src/packages/util/smc-version.js
@@ -1,2 +1,2 @@
/* autogenerated by the update_version script */
-exports.version=1707629396;
+exports.version=1709012686;