new preferences scheme

svn path=/trunk/boinc/; revision=444
This commit is contained in:
David Anderson 2002-09-29 00:32:11 +00:00
parent c12b1df84b
commit cbf997aa71
23 changed files with 215 additions and 133 deletions

View File

@ -1992,3 +1992,34 @@ David Sept 26 2002
test_uc.php
tools/
add.C
David Sept 28 2002
- Clarified the way that preferences (global and project)
are stored in the database and in the core client,
and the protocol (part of scheduler RPC) for maintaining them.
See doc/prefs_impl.html
- Implemented the above: lots of small changes to client, server
- Changed names from "prefs" to "global_prefs" where relevant
client/
client_state.C,h
client_types.C,h
cs_scheduler.C
file_names.C,h
prefs.C,h
scheduler_op.C,h
db/
db.h
db_mysql.C
schema.sql
doc/
boinc_dev.html
prefs_impl.html (new)
prefs_mod.html (removed)
html_user/
prefs.inc
sched/
handle_request.C
server_types.C,h
tools/
add.C

View File

@ -64,20 +64,11 @@ int CLIENT_STATE::init() {
//
nslots = 1;
// Read the user preferences file, if it exists.
// Read the global preferences file, if it exists.
//
retval = prefs.parse_file();
retval = global_prefs.parse_file();
if (retval) {
retval = write_initial_prefs();
if (retval) {
printf("can't initialize prefs.xml\n");
return retval;
}
retval = prefs.parse_file();
if (retval) {
printf("can't initialize prefs.xml\n");
return retval;
}
printf("No global preferences file; will use defaults.\n");
}
// parse account files.
@ -192,11 +183,14 @@ double CLIENT_STATE::allowed_disk_usage() {
double percent_space, min_val;
// Calculate allowed disk usage based on % pref
percent_space = host_info.d_total*prefs.disk_max_used_pct/100.0;
//
percent_space = host_info.d_total*global_prefs.disk_max_used_pct/100.0;
min_val = host_info.d_free - global_prefs.disk_min_free_gb*1e9;
min_val = host_info.d_free - prefs.disk_min_free_gb*1e9;
// Return the minimum of the three
return min(min(prefs.disk_max_used_gb*1e9, percent_space), min_val);
//
return min(min(global_prefs.disk_max_used_gb*1e9, percent_space), min_val);
}
// See if (on the basis of user prefs) we should suspend activities.
@ -204,7 +198,7 @@ double CLIENT_STATE::allowed_disk_usage() {
//
int CLIENT_STATE::check_suspend_activities() {
bool should_suspend = false;
if (prefs.dont_run_on_batteries && host_is_running_on_batteries()) {
if (global_prefs.dont_run_on_batteries && host_is_running_on_batteries()) {
should_suspend = true;
}

View File

@ -53,7 +53,7 @@ public:
FILE_XFER_SET* file_xfers;
ACTIVE_TASK_SET active_tasks;
HOST_INFO host_info;
PREFS prefs;
GLOBAL_PREFS global_prefs;
NET_STATS net_stats;
CLIENT_STATE();

View File

@ -64,6 +64,7 @@ int PROJECT::parse_account(FILE* in) {
strcpy(master_url, "");
strcpy(authenticator, "");
if (project_specific_prefs) free(project_specific_prefs);
while (fgets(buf, 256, in)) {
if (match_tag(buf, "<account>")) continue;
if (match_tag(buf, "</account>")) return 0;

View File

@ -51,7 +51,7 @@ public:
char master_url[256]; // url of site that contains scheduler tags
// for this project
char authenticator[256]; // user's authenticator on this project
char* project_specific_prefs;
char* project_specific_prefs; // without enclosing tags
double resource_share; // project's resource share
// relative to other projects. Arbitrary scale.

View File

@ -64,8 +64,8 @@ double CLIENT_STATE::current_water_days() {
//
double CLIENT_STATE::work_needed_secs() {
double x = current_water_days();
if (x > prefs.high_water_days) return 0;
return (prefs.high_water_days - x)*86400;
if (x > global_prefs.high_water_days) return 0;
return (global_prefs.high_water_days - x)*86400;
}
// update exponentially-averaged CPU times of all projects
@ -185,10 +185,13 @@ int CLIENT_STATE::make_scheduler_request(PROJECT* p, double work_req) {
fprintf(f, "<code_sign_key>\n%s</code_sign_key>\n", p->code_sign_key);
}
FILE* fprefs = fopen(PREFS_FILE_NAME, "r");
if (!fprefs) return ERR_FOPEN;
copy_stream(fprefs, f);
fclose(fprefs);
// insert global preferences if present
//
FILE* fprefs = fopen(GLOBAL_PREFS_FILE_NAME, "r");
if (fprefs) {
copy_stream(fprefs, f);
fclose(fprefs);
}
time_stats.write(f, true);
net_stats.write(f, true);
@ -247,7 +250,7 @@ bool CLIENT_STATE::scheduler_rpc_poll() {
switch(scheduler_op->state) {
case SCHEDULER_OP_STATE_IDLE:
below_low_water = (current_water_days() <= prefs.low_water_days);
below_low_water = (current_water_days() <= global_prefs.low_water_days);
if (below_low_water && some_project_rpc_ok()) {
compute_resource_debts();
scheduler_op->init_get_work();
@ -276,7 +279,7 @@ bool CLIENT_STATE::scheduler_rpc_poll() {
return action;
}
// Parse the reply from a scheduler
// Handle the reply from a scheduler
//
void CLIENT_STATE::handle_scheduler_reply(
PROJECT* project, char* scheduler_url
@ -315,32 +318,52 @@ void CLIENT_STATE::handle_scheduler_reply(
if (sr.request_delay) {
project->min_rpc_time = time(0) + sr.request_delay;
}
if (sr.hostid) {
project->hostid = sr.hostid;
project->rpc_seqno = 0;
}
// if the scheduler reply includes preferences
// that are newer than what we have on disk, write them to disk
// if the scheduler reply includes global preferences,
// insert extra elements, write to disk, and parse
//
if (sr.prefs_mod_time > prefs.mod_time) {
f = fopen(PREFS_FILE_NAME, "w");
if (sr.global_prefs_xml) {
f = fopen(GLOBAL_PREFS_FILE_NAME, "w");
fprintf(f,
"<preferences>\n"
" <prefs_mod_time>%d</prefs_mod_time>\n"
" <from_project>%s</from_project>\n"
" <from_scheduler>%s</from_scheduler>\n",
sr.prefs_mod_time,
"<global_preferences>\n"
" <source_project>%s</source_project>\n"
" <source_scheduler>%s</source_scheduler>\n"
"%s"
"</global_preferences>\n",
project->master_url,
scheduler_url
);
fputs(sr.prefs_xml, f);
fprintf(f,
"</preferences>\n"
scheduler_url,
sr.global_prefs_xml
);
fclose(f);
prefs.parse_file();
global_prefs.parse_file();
}
// deal with project preferences (should always be there)
//
if (sr.project_prefs_xml) {
char path[256];
f = fopen(TEMP_FILE_NAME, "w");
fprintf(f,
"<account>\n"
" <master_url>%s</master_url>\n"
" <authenticator>%s</authenticator>\n"
"%s"
"</account>\n",
project->master_url,
project->authenticator,
sr.project_prefs_xml
);
fclose(f);
get_account_filename(project->master_url, path);
retval = boinc_rename(TEMP_FILE_NAME, path);
f = fopen(path, "r");
project->parse_account(f);
fclose(f);
}
// if the scheduler reply includes a code-signing key,

View File

@ -189,13 +189,6 @@ int make_slot_dir(int slot) {
#endif
// Returns a filename used for prefs backup
//
int make_prefs_backup_name(PREFS& prefs, char* name) {
sprintf(name, "prefs_backup_%d", prefs.mod_time);
return 0;
}
void get_account_filename(char* master_url, char* path) {
char buf[256];
escape_project_url(master_url, buf);

View File

@ -31,7 +31,6 @@ extern void get_slot_dir(int slot, char* path);
extern int make_project_dir(PROJECT&);
extern int make_slot_dir(int);
extern int make_prefs_backup_name(PREFS&, char*);
extern void get_account_filename(char* master_url, char* path);
extern bool is_account_file(char*);
@ -39,9 +38,9 @@ extern bool is_account_file(char*);
#define SLOTS_DIR "slots"
#define STATE_FILE_TEMP "state_file_temp.xml"
#define STATE_FILE_NAME "client_state.xml"
#define PREFS_FILE_NAME "prefs.xml"
#define GLOBAL_PREFS_FILE_NAME "global_prefs.xml"
#define MASTER_FILE_NAME "master.html"
#define PREFS_TEMP_FILE_NAME "prefs_temp.xml"
#define SCHED_OP_REQUEST_FILE "sched_request.xml"
#define SCHED_OP_RESULT_FILE "sched_reply.xml"
#define LOG_FLAGS_FILE "log_flags.xml"
#define TEMP_FILE_NAME "temp.xml"

View File

@ -31,10 +31,9 @@
#include "prefs.h"
// the following values determine how the client behaves
// if the user didn't set preferences via web
// if there are no global prefs yet
//
PREFS::PREFS() {
mod_time = 0;
GLOBAL_PREFS::GLOBAL_PREFS() {
dont_run_on_batteries = false;
dont_run_if_user_active = false;
confirm_before_connecting = false;
@ -45,14 +44,13 @@ PREFS::PREFS() {
disk_min_free_gb = 0.1;
};
// Parse XML based prefs, usually from prefs.xml
// Parse XML global prefs
//
int PREFS::parse(FILE* in) {
int GLOBAL_PREFS::parse(FILE* in) {
char buf[256];
PROJECT* project;
while (fgets(buf, 256, in)) {
if (match_tag(buf, "</preferences>")) {
if (match_tag(buf, "</global_preferences>")) {
return 0;
} else if (match_tag(buf, "<dont_run_on_batteries/>")) {
dont_run_on_batteries = true;
@ -78,32 +76,15 @@ int PREFS::parse(FILE* in) {
return ERR_XML_PARSE;
}
// Parse prefs.xml for user preferences
// Parse global prefs file
//
int PREFS::parse_file() {
int GLOBAL_PREFS::parse_file() {
FILE* f;
int retval;
f = fopen(PREFS_FILE_NAME, "r");
f = fopen(GLOBAL_PREFS_FILE_NAME, "r");
if (!f) return ERR_FOPEN;
retval = parse(f);
fclose(f);
return retval;
}
// Write the default preferences
// TODO: should mod_time really be 1?
//
int write_initial_prefs() {
FILE* f = fopen(PREFS_FILE_NAME, "w");
if (!f) return ERR_FOPEN;
fprintf(f,
"<preferences>\n"
" <mod_time>1</mod_time>\n"
" <high_water_days>2</high_water_days>\n"
" <low_water_days>1</low_water_days>\n"
"</preferences>\n"
);
fclose(f);
return 0;
}

View File

@ -33,8 +33,7 @@
// The following structure is a parsed version of the prefs file
//
struct PREFS {
int mod_time;
struct GLOBAL_PREFS {
bool dont_run_on_batteries;
bool dont_run_if_user_active;
bool confirm_before_connecting;
@ -44,11 +43,9 @@ struct PREFS {
double disk_max_used_pct;
double disk_min_free_gb;
PREFS();
GLOBAL_PREFS();
int parse(FILE*);
int parse_file();
};
extern int write_initial_prefs();
#endif

View File

@ -340,13 +340,15 @@ bool SCHEDULER_OP::poll() {
}
SCHEDULER_REPLY::SCHEDULER_REPLY() {
prefs_xml = 0;
global_prefs_xml = 0;
project_prefs_xml = 0;
code_sign_key = 0;
code_sign_key_signature = 0;
}
SCHEDULER_REPLY::~SCHEDULER_REPLY() {
if (prefs_xml) free(prefs_xml);
if (global_prefs_xml) free(global_prefs_xml);
if (project_prefs_xml) free(project_prefs_xml);
if (code_sign_key) free(code_sign_key);
if (code_sign_key_signature) free(code_sign_key_signature);
}
@ -359,8 +361,8 @@ int SCHEDULER_REPLY::parse(FILE* in) {
strcpy(message_priority, "");
request_delay = 0;
hostid = 0;
prefs_mod_time = 0;
prefs_xml = 0;
global_prefs_xml = 0;
project_prefs_xml = 0;
code_sign_key = 0;
code_sign_key_signature = 0;
@ -388,10 +390,11 @@ int SCHEDULER_REPLY::parse(FILE* in) {
continue;
} else if (parse_int(buf, "<request_delay>", request_delay)) {
continue;
} else if (parse_int(buf, "<prefs_mod_time>", prefs_mod_time)) {
continue;
} else if (match_tag(buf, "<preferences>")) {
retval = dup_element_contents(in, "</preferences>", &prefs_xml);
} else if (match_tag(buf, "<global_preferences>")) {
retval = dup_element_contents(in, "</global_preferences>", &global_prefs_xml);
if (retval) return ERR_XML_PARSE;
} else if (match_tag(buf, "<project_preferences>")) {
retval = dup_element_contents(in, "</project_preferences>", &project_prefs_xml);
if (retval) return ERR_XML_PARSE;
} else if (match_tag(buf, "<code_sign_key>")) {
retval = dup_element_contents(in, "</code_sign_key>", &code_sign_key);

View File

@ -76,8 +76,8 @@ struct SCHEDULER_REPLY {
char message[1024];
char message_priority[256];
char project_name[256];
int prefs_mod_time;
char* prefs_xml;
char* global_prefs_xml; // not including <global_preferences> tags
char* project_prefs_xml; // not including <project_preferences> tags
char user_name[256];
double total_credit;
double expavg_credit;

View File

@ -116,10 +116,10 @@ struct USER {
double expavg_credit; // credit per second, recent average
double expavg_time; // when the above was computed
char global_prefs[MAX_BLOB_SIZE]; // global preferences
unsigned int global_prefs_mod_time; // When global prefs were last updated
// zero if they're not defined
// within <global_preferences> tag
char project_prefs[MAX_BLOB_SIZE];
int teamid; // if the user is part of a team
// within <project_preferences> tag
int teamid; // if user is part of a team
};
#define TEAM_TYPE_COMPANY_SMALL 1

View File

@ -120,7 +120,7 @@ void struct_to_str(void* vp, char* q, int type) {
"web_password='%s', authenticator='%s', "
"country='%s', postal_code='%s', "
"total_credit=%f, expavg_credit=%f, expavg_time=%f, "
"global_prefs='%s', global_prefs_mod_time=%d, project_prefs='%s', "
"global_prefs='%s', project_prefs='%s', "
"teamid=%d",
up->id,
up->create_time,
@ -134,7 +134,6 @@ void struct_to_str(void* vp, char* q, int type) {
up->expavg_credit,
up->expavg_time,
up->global_prefs,
up->global_prefs_mod_time,
up->project_prefs,
up->teamid
);
@ -296,7 +295,6 @@ void row_to_struct(MYSQL_ROW& r, void* vp, int type) {
up->expavg_credit = atof(r[i++]);
up->expavg_time = atof(r[i++]);
strcpy(up->global_prefs, r[i++]);
up->global_prefs_mod_time = atoi(r[i++]);
strcpy(up->project_prefs, r[i++]);
up->teamid = atoi(r[i++]);
break;

View File

@ -49,7 +49,6 @@ create table user (
expavg_credit float not null,
expavg_time float not null,
global_prefs blob,
global_prefs_mod_time integer not null,
project_prefs blob,
teamid integer not null,
primary key (id)

View File

@ -37,8 +37,11 @@ before getting into the source code.
<ul>
<li> <a href=protocol.html>The scheduling server protocol</a>
<li> <a href=rpc_policy.html>Scheduling server timing and retry policies</a>
<li> <a href=prefs_propagate.html>Preferences propagation</a>
<li> <a href=upload.html>Data server protocol</a>
<li> <a href=pers_file_xfer.html>Persistent file transfers</a>
</ul>
<li> Miscellaneous
<ul>
<li> <a href=prefs_impl.html>Preferences</a>
</ul>
</ul>

52
doc/prefs_impl.html Normal file
View File

@ -0,0 +1,52 @@
<pre>
In database:
user.global_prefs: XML, within <lt;global_preferences> tags
may be empty; nonempty only if user has actually seen
always includes <lt;mod_time> element
includes <lt;source_project>, <lt;source_scheduler> elements if
prefs came from another project
user.project_prefs: XML, within <lt;project_preferences> tags
always present.
contains at least <lt;resource_share> element
In client:
global_prefs.xml (present ONLY if have obtained from a server)
same as user.global_prefs,
but the following is inserted at start:
<lt;source_project>
<lt;source_scheduler>
stored in memory:
in parsed form (as struct)
account_XXX.xml
same as user.project_prefs, but with the following added:
<lt;master_url>
<lt;authenticator>
stored in memory:
in PROJECT struct
master_url, authenticator, resource share parsed;
project_specific_prefs raw XML (with enclosing tags)
RPC request:
includes global_prefs.xml if it's there
RPC handling and reply:
always send project prefs
if request message includes global prefs
if missing in DB, or request copy is newer:
install in DB
else if DB copy is newer
include DB copy in reply
else
if present in DB, include in reply
handling of RPC reply
if includes global prefs
insert <lt;source_project>, <lt;source_scheduler> elements if missing
write to global_prefs.xml
parse into memory
project prefs
insert <lt;master_url>, <lt;authenticator> elements,
write to account_XXX.xml file
parse; update resource share, project_specific_prefs in PROJECT struct
</pre>

View File

@ -1,4 +0,0 @@
<title>Modifying Preferences</title>
<body bgcolor=ffffff>
<h2>Modifying Preferences</h2>
<p>

View File

@ -287,6 +287,8 @@ function prefs_project_parse_form(&$prefs) {
//
function global_prefs_make_xml($prefs) {
$xml = "<global_preferences>\n";
$now = time();
$xml = $xml."<mod_time>$now</mod_time>\n";
if ($prefs->dont_run_on_batteries) {
$xml = $xml."<dont_run_on_batteries/>\n";
}
@ -322,14 +324,12 @@ function project_prefs_make_xml($prefs) {
//
function global_prefs_update($user, $prefs) {
$prefs_xml = global_prefs_make_xml($prefs);
$now = time();
mysql_query("update user set global_prefs='$prefs_xml', global_prefs_mod_time=$now where id=$user->id");
mysql_query("update user set global_prefs='$prefs_xml' where id=$user->id");
$user->global_prefs = $prefs_xml;
}
function project_prefs_update($user, $prefs) {
$prefs_xml = project_prefs_make_xml($prefs);
$now = time();
mysql_query("update user set project_prefs='$prefs_xml' where id=$user->id");
$user->project_prefs = $prefs_xml;
}

View File

@ -224,23 +224,39 @@ int update_host_record(SCHEDULER_REQUEST& sreq, HOST& host) {
return 0;
}
// Deal with global preferences.
// If the client sent global prefs, and they're more recent than ours,
// update user record in DB.
// If we our DB has more recent global prefs than client's, send them.
// If DB has more recent global prefs than client's, send them.
//
int handle_global_prefs(SCHEDULER_REQUEST& sreq, SCHEDULER_REPLY& reply) {
if (sreq.global_prefs_mod_time > reply.user.global_prefs_mod_time
&& strlen(sreq.global_prefs_xml)
) {
strncpy(reply.user.global_prefs, sreq.global_prefs_xml, sizeof(reply.user.global_prefs));
reply.user.global_prefs_mod_time = sreq.global_prefs_mod_time;
if (reply.user.global_prefs_mod_time > (unsigned)time(0)) {
reply.user.global_prefs_mod_time = (unsigned)time(0);
unsigned int req_mod_time, db_mod_time;
bool need_update;
reply.send_global_prefs = false;
if (sreq.global_prefs_xml) {
need_update = false;
parse_int(sreq.global_prefs_xml, "<mod_time>", (int)req_mod_time);
if (strlen(reply.user.global_prefs)) {
parse_int(reply.user.global_prefs, "<mod_time>", (int)db_mod_time);
if (req_mod_time > db_mod_time) {
need_update = true;
} else if (req_mod_time < db_mod_time) {
reply.send_global_prefs = true;
}
} else {
need_update = true;
}
if (need_update) {
strncpy(
reply.user.global_prefs, sreq.global_prefs_xml,
sizeof(reply.user.global_prefs)
);
db_user_update(reply.user);
}
} else {
if (strlen(reply.user.global_prefs)) {
reply.send_global_prefs = true;
}
db_user_update(reply.user);
}
if (reply.user.global_prefs_mod_time > sreq.global_prefs_mod_time) {
reply.send_global_prefs = true;
}
return 0;
}

View File

@ -46,7 +46,6 @@ int SCHEDULER_REQUEST::parse(FILE* fin) {
strcpy(authenticator, "");
hostid = 0;
work_req_seconds = 0;
global_prefs_mod_time = 0;
global_prefs_xml = strdup("");
fgets(buf, 256, fin);
@ -59,14 +58,13 @@ int SCHEDULER_REQUEST::parse(FILE* fin) {
else if (parse_str(buf, "<platform_name>", platform_name, sizeof(platform_name))) continue;
else if (parse_int(buf, "<core_client_version>", core_client_version)) continue;
else if (parse_int(buf, "<work_req_seconds>", work_req_seconds)) continue;
else if (parse_int(buf, "<global_prefs_mod_time>", (int)global_prefs_mod_time)) {
continue;
}
else if (match_tag(buf, "<preferences>")) {
else if (match_tag(buf, "<global_preferences>")) {
global_prefs_xml = strdup("<global_preferences>\n");
while (fgets(buf, 256, fin)) {
if (strstr(buf, "</preferences>")) break;
if (strstr(buf, "</global_preferences>")) break;
strcatdup(global_prefs_xml, buf);
}
strcatdup(global_prefs_xml, "</global_preferences>\n");
}
else if (match_tag(buf, "<host_info>")) {
host.parse(fin);
@ -145,13 +143,13 @@ int SCHEDULER_REPLY::write(FILE* fout) {
}
if (send_global_prefs) {
fprintf(fout,
"<global_prefs_mod_time>%d</global_prefs_mod_time>\n",
user.global_prefs_mod_time
);
fputs(user.global_prefs, fout);
}
// always send project prefs
//
fputs(user.project_prefs, fout);
// acknowledge results
//
for (i=0; i<result_acks.size(); i++) {

View File

@ -32,7 +32,6 @@ struct SCHEDULER_REQUEST {
int core_client_version;
int rpc_seqno;
int work_req_seconds;
unsigned int global_prefs_mod_time;
char* global_prefs_xml;
char* code_sign_key;

View File

@ -237,7 +237,6 @@ void add_user() {
printf("read_file: %s", global_prefs_file);
return;
}
user.global_prefs_mod_time = time(0);
}
retval = db_user_new(user);
if (retval) {