CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/course/sync.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
// Describes how the client course editor syncs with the database
7
8
import { fromJS } from "immutable";
9
import { callback2 } from "@cocalc/util/async-utils";
10
11
// SMC libraries
12
import * as misc from "@cocalc/util/misc";
13
import { webapp_client } from "../webapp-client";
14
import { SyncDB } from "@cocalc/sync/editor/db/sync";
15
import { CourseActions } from "./actions";
16
import { CourseStore } from "./store";
17
import { AppRedux } from "../app-framework";
18
19
export function create_sync_db(
20
redux: AppRedux,
21
actions: CourseActions,
22
store: CourseStore,
23
filename: string,
24
): SyncDB {
25
if (redux == null || actions == null || store == null) {
26
// just in case non-typescript code uses this...
27
throw Error("redux, actions and store must not be null");
28
}
29
30
const project_id = store.get("course_project_id");
31
const path = store.get("course_filename");
32
actions.setState({ loading: true });
33
34
const syncdb = webapp_client.sync_client.sync_db({
35
project_id,
36
path,
37
primary_keys: ["table", "handout_id", "student_id", "assignment_id"],
38
string_cols: ["note", "description", "title", "email_invite"],
39
change_throttle: 500, // helps when doing a lot of assign/collect, etc.
40
});
41
42
syncdb.once("error", (err) => {
43
if (!actions.is_closed()) {
44
actions.set_error(err);
45
}
46
console.warn(`Error using '${store.get("course_filename")}' -- ${err}`);
47
});
48
49
syncdb.once("ready", async () => {
50
const i = store.get("course_filename").lastIndexOf(".");
51
const t = {
52
settings: {
53
title: store.get("course_filename").slice(0, i),
54
description: "No description",
55
allow_collabs: true,
56
},
57
assignments: {},
58
students: {},
59
handouts: {},
60
loading: false,
61
};
62
for (const x of syncdb.get().toJS()) {
63
if (x.table === "settings") {
64
misc.merge(t.settings, misc.copy_without(x, "table"));
65
} else if (x.table === "students") {
66
t.students[x.student_id] = misc.copy_without(x, "table");
67
} else if (x.table === "assignments") {
68
t.assignments[x.assignment_id] = misc.copy_without(x, "table");
69
} else if (x.table === "handouts") {
70
t.handouts[x.handout_id] = misc.copy_without(x, "table");
71
}
72
}
73
for (const k in t) {
74
const v = t[k];
75
t[k] = fromJS(v);
76
}
77
if (!actions.is_closed()) {
78
(actions as any).setState(t); // TODO: as any since t is an object, not immutable.js map...
79
}
80
syncdb.on("change", (changes) => {
81
if (!actions.is_closed()) {
82
actions.syncdb_change(changes);
83
}
84
});
85
86
syncdb.on("after-change", () =>
87
redux.getProjectActions(project_id).flag_file_activity(filename),
88
);
89
90
const course_project_id = store.get("course_project_id");
91
const p = redux.getProjectActions(course_project_id);
92
if (p != null) {
93
p.log_opened_time(store.get("course_filename"));
94
}
95
96
// Wait until the projects store has data about users of our project before configuring anything.
97
const projects_store = redux.getStore("projects");
98
try {
99
await callback2(projects_store.wait, {
100
until(p_store) {
101
return p_store.get_users(project_id) != null;
102
},
103
timeout: 60,
104
});
105
} catch (err) {
106
return; // something is very broken (or maybe admin view)...
107
}
108
if (actions.is_closed()) {
109
return;
110
}
111
// compute image default setup
112
const course_compute_image = store.getIn(["settings", "custom_image"]);
113
const inherit_compute_image =
114
store.getIn(["settings", "inherit_compute_image"]) ?? true;
115
// if the compute image isn't set or should be inherited, we configure it for all controlled projects
116
if (course_compute_image == null || inherit_compute_image) {
117
const course_project_compute_image = projects_store.getIn([
118
"project_map",
119
course_project_id,
120
"compute_image",
121
]);
122
actions.set({
123
custom_image: course_project_compute_image,
124
inherit_compute_image,
125
table: "settings",
126
});
127
}
128
129
// datastore default setup
130
const datastore = store.getIn(["settings", "datastore"]);
131
if (datastore == null) {
132
actions.set({
133
datastore: true,
134
table: "settings",
135
});
136
}
137
138
await actions.configuration.configure_all_projects();
139
140
// Also
141
projects_store.on(
142
"change",
143
actions.handle_projects_store_update.bind(actions),
144
);
145
actions.handle_projects_store_update(projects_store);
146
147
// Handle deprecation
148
if (store.getIn(["settings", "nbgrader_grade_in_instructor_project"])) {
149
actions.set({
150
nbgrader_grade_in_instructor_project: false,
151
nbgrader_grade_project: course_project_id,
152
table: "settings",
153
});
154
}
155
});
156
157
return syncdb;
158
}
159
160