+
+ byte *pname = obj_find_aval(c->request, 'P');
+ if (!pname)
+ {
+ simp_node *s = clist_head(&task->parts);
+ ASSERT(s);
+ pname = s->s;
+ }
+ else if (!part_exists_p(task, pname))
+ {
+ err(c, "No such task part");
+ return;
+ }
+
+ byte *ext = obj_find_aval(c->request, 'X');
+ if (!ext || !ext_exists_p(task, ext))
+ {
+ err(c, "Missing or invalid extension");
+ return;
+ }
+
+ uns max_size = task->max_size ? : max_attachment_size;
+ struct fastbuf *fb = read_attachment(c, max_size);
+ if (!fb)
+ return;
+
+ task_lock_status(c);
+ struct odes *tasko = task_status_find_task(c, task, 1);
+ struct odes *parto = task_status_find_part(tasko, pname, 1);
+ uns current_ver = obj_find_anum(parto, 'V', 0);
+ if (current_ver >= max_versions)
+ {
+ err(c, "Maximum number of submits of this task exceeded");
+ bclose(fb);
+ task_unlock_status(c, 0);
+ return;
+ }
+ uns last_ver = 0;
+ uns replaced_ver = 0;
+ for (struct oattr *a = obj_find_attr(parto, 'V' + OBJ_ATTR_SON); a; a=a->same)
+ {
+ uns ver = obj_find_anum(a->son, 'V', 0);
+ byte *ext = obj_find_aval(a->son, 'X');
+ ASSERT(ver && ext);
+ last_ver = MAX(last_ver, ver);
+ if (ver == current_ver)
+ {
+ task_delete_part(c->user, tname, pname, ext, ver);
+ obj_set_attr(a->son, 'S', "replaced");
+ replaced_ver = current_ver;
+ }
+ }
+ struct odes *vero = obj_add_son(parto, 'V' + OBJ_ATTR_SON);
+ obj_set_attr_num(vero, 'V', ++last_ver);
+ obj_set_attr_num(vero, 'T', time(NULL));
+ obj_set_attr_num(vero, 'L', obj_find_anum(c->request, 'S', 0));
+ obj_set_attr(vero, 'S', "submitted");
+ obj_set_attr(vero, 'X', ext);
+ task_submit_part(c->user, tname, pname, ext, last_ver, fb);
+ obj_set_attr_num(parto, 'V', last_ver);
+ task_unlock_status(c, 1);
+
+ msg(L_INFO, "User %s submitted task %s%s (version %d%s)",
+ c->user, tname,
+ (strcmp(tname, pname) ? stk_printf("/%s", pname) : ""),
+ last_ver,
+ (replaced_ver ? stk_printf(", replaced %d", replaced_ver) : ""));