diff --git a/common/rpc-service.c b/common/rpc-service.c index 66c6fd84..200cbf18 100644 --- a/common/rpc-service.c +++ b/common/rpc-service.c @@ -2548,6 +2548,7 @@ seafile_post_multi_files (const char *repo_id, paths_json, user, replace_existed, + 0, &ret_json, NULL, error); @@ -2598,6 +2599,7 @@ seafile_put_file (const char *repo_id, const char *temp_file_path, seaf_repo_manager_put_file (seaf->repo_mgr, repo_id, temp_file_path, rpath, norm_file_name, user, head_id, + 0, &new_file_id, error); out: diff --git a/fileserver/fileop.go b/fileserver/fileop.go index 38b684a5..5c258a3d 100644 --- a/fileserver/fileop.go +++ b/fileserver/fileop.go @@ -1003,6 +1003,15 @@ func doUpload(rsp http.ResponseWriter, r *http.Request, fsm *recvData, isAjax bo return &appError{nil, msg, http.StatusBadRequest} } + lastModifyStr := normalizeUTF8Path(r.FormValue("last_modify")) + var lastModify int64 + if lastModifyStr != "" { + t, err := time.Parse(time.RFC3339, lastModifyStr) + if err == nil { + lastModify = t.Unix() + } + } + relativePath := normalizeUTF8Path(r.FormValue("relative_path")) if relativePath != "" { if relativePath[0] == '/' || relativePath[0] == '\\' { @@ -1115,7 +1124,7 @@ func doUpload(rsp http.ResponseWriter, r *http.Request, fsm *recvData, isAjax bo } if err := postMultiFiles(rsp, r, repoID, newParentDir, user, fsm, - replaceExisted, isAjax); err != nil { + replaceExisted, lastModify, isAjax); err != nil { return err } @@ -1460,7 +1469,7 @@ func parseUploadHeaders(r *http.Request) (*recvData, *appError) { return fsm, nil } -func postMultiFiles(rsp http.ResponseWriter, r *http.Request, repoID, parentDir, user string, fsm *recvData, replace bool, isAjax bool) *appError { +func postMultiFiles(rsp http.ResponseWriter, r *http.Request, repoID, parentDir, user string, fsm *recvData, replace bool, lastModify int64, isAjax bool) *appError { fileNames := fsm.fileNames files := fsm.files @@ -1528,7 +1537,7 @@ func postMultiFiles(rsp http.ResponseWriter, r *http.Request, repoID, parentDir, } } - retStr, err := postFilesAndGenCommit(fileNames, repo.ID, user, canonPath, replace, ids, sizes) + retStr, err := postFilesAndGenCommit(fileNames, repo.ID, user, canonPath, replace, ids, sizes, lastModify) if err != nil { err := fmt.Errorf("failed to post files and gen commit: %v", err) return &appError{err, "", http.StatusInternalServerError} @@ -1584,7 +1593,7 @@ func checkFilesWithSameName(repo *repomgr.Repo, canonPath string, fileNames []st return false } -func postFilesAndGenCommit(fileNames []string, repoID string, user, canonPath string, replace bool, ids []string, sizes []int64) (string, error) { +func postFilesAndGenCommit(fileNames []string, repoID string, user, canonPath string, replace bool, ids []string, sizes []int64, lastModify int64) (string, error) { repo := repomgr.Get(repoID) if repo == nil { err := fmt.Errorf("failed to get repo %s", repoID) @@ -1604,7 +1613,10 @@ func postFilesAndGenCommit(fileNames []string, repoID string, user, canonPath st break } mode := (syscall.S_IFREG | 0644) - mtime := time.Now().Unix() + mtime := lastModify + if mtime <= 0 { + mtime = time.Now().Unix() + } dent := fsmgr.NewDirent(ids[i], name, uint32(mode), mtime, "", sizes[i]) dents = append(dents, dent) } @@ -2763,6 +2775,15 @@ func doUpdate(rsp http.ResponseWriter, r *http.Request, fsm *recvData, isAjax bo return &appError{nil, msg, http.StatusBadRequest} } + lastModifyStr := normalizeUTF8Path(r.FormValue("last_modify")) + var lastModify int64 + if lastModifyStr != "" { + t, err := time.Parse(time.RFC3339, lastModifyStr) + if err == nil { + lastModify = t.Unix() + } + } + parentDir := filepath.Dir(targetFile) fileName := filepath.Base(targetFile) @@ -2869,7 +2890,7 @@ func doUpdate(rsp http.ResponseWriter, r *http.Request, fsm *recvData, isAjax bo headID = headIDs[0] } - if err := putFile(rsp, r, repoID, parentDir, user, fileName, fsm, headID, isAjax); err != nil { + if err := putFile(rsp, r, repoID, parentDir, user, fileName, fsm, headID, lastModify, isAjax); err != nil { return err } @@ -2879,7 +2900,7 @@ func doUpdate(rsp http.ResponseWriter, r *http.Request, fsm *recvData, isAjax bo return nil } -func putFile(rsp http.ResponseWriter, r *http.Request, repoID, parentDir, user, fileName string, fsm *recvData, headID string, isAjax bool) *appError { +func putFile(rsp http.ResponseWriter, r *http.Request, repoID, parentDir, user, fileName string, fsm *recvData, headID string, lastModify int64, isAjax bool) *appError { files := fsm.files repo := repomgr.Get(repoID) if repo == nil { @@ -2974,6 +2995,9 @@ func putFile(rsp http.ResponseWriter, r *http.Request, repoID, parentDir, user, } mtime := time.Now().Unix() + if lastModify > 0 { + mtime = lastModify + } mode := (syscall.S_IFREG | 0644) newDent := fsmgr.NewDirent(fileID, fileName, uint32(mode), mtime, user, size) @@ -3086,6 +3110,15 @@ func doUploadBlks(rsp http.ResponseWriter, r *http.Request, fsm *recvData) *appE return &appError{nil, msg, http.StatusBadRequest} } + lastModifyStr := normalizeUTF8Path(r.FormValue("last_modify")) + var lastModify int64 + if lastModifyStr != "" { + t, err := time.Parse(time.RFC3339, lastModifyStr) + if err == nil { + lastModify = t.Unix() + } + } + fileName := normalizeUTF8Path(r.FormValue("file_name")) if fileName == "" { msg := "No file_name given.\n" @@ -3124,7 +3157,7 @@ func doUploadBlks(rsp http.ResponseWriter, r *http.Request, fsm *recvData) *appE return &appError{nil, msg, http.StatusBadRequest} } - fileID, appErr := commitFileBlocks(repoID, parentDir, fileName, blockIDsJSON, user, fileSize, replaceExisted) + fileID, appErr := commitFileBlocks(repoID, parentDir, fileName, blockIDsJSON, user, fileSize, replaceExisted, lastModify) if appErr != nil { return appErr } @@ -3150,7 +3183,7 @@ func doUploadBlks(rsp http.ResponseWriter, r *http.Request, fsm *recvData) *appE return nil } -func commitFileBlocks(repoID, parentDir, fileName, blockIDsJSON, user string, fileSize int64, replace bool) (string, *appError) { +func commitFileBlocks(repoID, parentDir, fileName, blockIDsJSON, user string, fileSize int64, replace bool, lastModify int64) (string, *appError) { repo := repomgr.Get(repoID) if repo == nil { msg := "Failed to get repo.\n" @@ -3195,6 +3228,9 @@ func commitFileBlocks(repoID, parentDir, fileName, blockIDsJSON, user string, fi } mtime := time.Now().Unix() + if lastModify > 0 { + mtime = lastModify + } mode := (syscall.S_IFREG | 0644) newDent := fsmgr.NewDirent(fileID, fileName, uint32(mode), mtime, user, fileSize) var names []string diff --git a/server/index-blocks-mgr.c b/server/index-blocks-mgr.c index 32cfbe60..be9839d3 100644 --- a/server/index-blocks-mgr.c +++ b/server/index-blocks-mgr.c @@ -178,6 +178,7 @@ start_index_task (gpointer data, gpointer user_data) idx_para->canon_path, id_list, size_list, + 0, NULL); progress->status = ret; if (idx_para->ret_json) { diff --git a/server/repo-mgr.h b/server/repo-mgr.h index cb987fc3..70b8577d 100644 --- a/server/repo-mgr.h +++ b/server/repo-mgr.h @@ -329,6 +329,7 @@ seaf_repo_manager_post_multi_files (SeafRepoManager *mgr, const char *paths_json, const char *user, int replace_existed, + gint64 mtime, char **new_ids, char **task_id, GError **error); @@ -363,6 +364,7 @@ seaf_repo_manager_commit_file_blocks (SeafRepoManager *mgr, const char *user, gint64 file_size, int replace_existed, + gint64 mtime, char **new_id, GError **error); @@ -405,6 +407,7 @@ seaf_repo_manager_put_file (SeafRepoManager *mgr, const char *file_name, const char *user, const char *head_id, + gint64 mtime, char **new_file_id, GError **error); @@ -916,6 +919,7 @@ post_files_and_gen_commit (GList *filenames, const char *canon_path, GList *id_list, GList *size_list, + gint64 mtime, GError **error); char * diff --git a/server/repo-op.c b/server/repo-op.c index 1cfe1c98..3ab2356e 100644 --- a/server/repo-op.c +++ b/server/repo-op.c @@ -51,6 +51,7 @@ post_files_and_gen_commit (GList *filenames, const char *canon_path, GList *id_list, GList *size_list, + gint64 mtime, GError **error); /* @@ -904,6 +905,7 @@ do_post_multi_files (SeafRepo *repo, GList *size_list, const char *user, int replace_existed, + gint64 mtime, GList **name_list) { SeafDirent *dent; @@ -925,7 +927,11 @@ do_post_multi_files (SeafRepo *repo, dent->id[40] = '\0'; dent->size = *size; dent->mode = STD_FILE_MODE; - dent->mtime = (gint64)time(NULL); + if (mtime > 0) { + dent->mtime = mtime; + } else { + dent->mtime = (gint64)time(NULL); + } dents = g_list_append (dents, dent); } @@ -1069,6 +1075,7 @@ seaf_repo_manager_post_multi_files (SeafRepoManager *mgr, const char *paths_json, const char *user, int replace_existed, + gint64 mtime, char **ret_json, char **task_id, GError **error) @@ -1169,6 +1176,7 @@ seaf_repo_manager_post_multi_files (SeafRepoManager *mgr, canon_path, id_list, size_list, + mtime, error); } else { ret = index_blocks_mgr_start_index (seaf->index_blocks_mgr, @@ -1207,6 +1215,7 @@ post_files_and_gen_commit (GList *filenames, const char *canon_path, GList *id_list, GList *size_list, + gint64 mtime, GError **error) { SeafRepo *repo = NULL; @@ -1224,7 +1233,7 @@ post_files_and_gen_commit (GList *filenames, /* Add the files to parent dir and commit. */ root_id = do_post_multi_files (repo, head_commit->root_id, canon_path, filenames, id_list, size_list, user, - replace_existed, &name_list); + replace_existed, mtime, &name_list); if (!root_id) { seaf_warning ("[post multi-file] Failed to post files to %s in repo %s.\n", canon_path, repo->id); @@ -1496,6 +1505,7 @@ seaf_repo_manager_commit_file_blocks (SeafRepoManager *mgr, const char *user, gint64 file_size, int replace_existed, + gint64 mtime, char **new_id, GError **error) { @@ -1554,9 +1564,12 @@ seaf_repo_manager_commit_file_blocks (SeafRepoManager *mgr, } rawdata_to_hex(sha1, hex, 20); + if (mtime <= 0) { + mtime = (gint64)time(NULL); + } new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version), hex, STD_FILE_MODE, file_name, - (gint64)time(NULL), user, file_size); + mtime, user, file_size); root_id = do_post_file_replace (repo, head_commit->root_id, canon_path, replace_existed, new_dent); @@ -4120,6 +4133,7 @@ seaf_repo_manager_put_file (SeafRepoManager *mgr, const char *file_name, const char *user, const char *head_id, + gint64 mtime, char **new_file_id, GError **error) { @@ -4197,9 +4211,12 @@ seaf_repo_manager_put_file (SeafRepoManager *mgr, } rawdata_to_hex(sha1, hex, 20); + if (mtime <= 0) { + mtime = (gint64)time(NULL); + } new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version), hex, STD_FILE_MODE, file_name, - (gint64)time(NULL), user, size); + mtime, user, size); if (!fullpath) fullpath = g_build_filename(parent_dir, file_name, NULL); diff --git a/server/upload-file.c b/server/upload-file.c index 7ba17656..cc057b43 100755 --- a/server/upload-file.c +++ b/server/upload-file.c @@ -422,12 +422,30 @@ file_id_list_from_json (const char *ret_json) return g_string_free (id_list, FALSE); } +static gint64 +rfc3339_to_timestamp (const char *last_modify) +{ + if (!last_modify) { + return -1; + } + GDateTime *date_time = g_date_time_new_from_iso8601(last_modify, NULL); + if (!date_time) { + return -1; + } + gint64 mtime = g_date_time_to_unix(date_time); + + g_date_time_unref(date_time); + return mtime; +} + static void upload_api_cb(evhtp_request_t *req, void *arg) { RecvFSM *fsm = arg; char *parent_dir, *replace_str; char *relative_path = NULL, *new_parent_dir = NULL; + char *last_modify = NULL; + gint64 mtime = 0; GError *error = NULL; int error_code = -1; char *filenames_json, *tmp_files_json; @@ -468,6 +486,11 @@ upload_api_cb(evhtp_request_t *req, void *arg) return; } + last_modify = g_hash_table_lookup (fsm->form_kvs, "last_modify"); + if (last_modify) { + mtime = rfc3339_to_timestamp (last_modify); + } + replace_str = g_hash_table_lookup (fsm->form_kvs, "replace"); if (replace_str) { replace = atoi(replace_str); @@ -575,6 +598,7 @@ upload_api_cb(evhtp_request_t *req, void *arg) tmp_files_json, fsm->user, replace, + mtime, &ret_json, fsm->need_idx_progress ? &task_id : NULL, &error); @@ -681,6 +705,8 @@ upload_blks_api_cb(evhtp_request_t *req, void *arg) { RecvFSM *fsm = arg; const char *parent_dir, *file_name, *size_str, *replace_str, *commitonly_str; + char *last_modify = NULL; + gint64 mtime = 0; GError *error = NULL; int error_code = -1; char *blockids_json; @@ -710,6 +736,11 @@ upload_blks_api_cb(evhtp_request_t *req, void *arg) file_size = atoll(size_str); commitonly_str = evhtp_kv_find (req->uri->query, "commitonly"); + last_modify = g_hash_table_lookup (fsm->form_kvs, "last_modify"); + if (last_modify) { + mtime = rfc3339_to_timestamp (last_modify); + } + if (!file_name || !parent_dir || !size_str || file_size < 0) { seaf_debug ("[upload-blks] No parent dir or file name given.\n"); send_error_reply (req, EVHTP_RES_BADREQ, "No parent dir or file name.\n"); @@ -768,6 +799,7 @@ upload_blks_api_cb(evhtp_request_t *req, void *arg) fsm->user, file_size, replace, + mtime, &new_file_id, &error); if (rc < 0) { @@ -1051,6 +1083,8 @@ upload_ajax_cb(evhtp_request_t *req, void *arg) { RecvFSM *fsm = arg; char *parent_dir = NULL, *relative_path = NULL, *new_parent_dir = NULL; + char *last_modify = NULL; + gint64 mtime = 0; GError *error = NULL; int error_code = -1; char *filenames_json, *tmp_files_json; @@ -1091,6 +1125,11 @@ upload_ajax_cb(evhtp_request_t *req, void *arg) return; } + last_modify = g_hash_table_lookup (fsm->form_kvs, "last_modify"); + if (last_modify) { + mtime = rfc3339_to_timestamp (last_modify); + } + if (!fsm->filenames) { seaf_debug ("[upload] No file uploaded.\n"); send_error_reply (req, EVHTP_RES_BADREQ, "No file uploaded.\n"); @@ -1190,6 +1229,7 @@ upload_ajax_cb(evhtp_request_t *req, void *arg) tmp_files_json, fsm->user, 0, + mtime, &ret_json, fsm->need_idx_progress ? &task_id : NULL, &error); @@ -1236,6 +1276,8 @@ update_api_cb(evhtp_request_t *req, void *arg) { RecvFSM *fsm = arg; char *target_file, *parent_dir = NULL, *filename = NULL; + char *last_modify = NULL; + gint64 mtime = 0; const char *head_id = NULL; GError *error = NULL; int error_code = -1; @@ -1278,6 +1320,11 @@ update_api_cb(evhtp_request_t *req, void *arg) return; } + last_modify = g_hash_table_lookup (fsm->form_kvs, "last_modify"); + if (last_modify) { + mtime = rfc3339_to_timestamp (last_modify); + } + parent_dir = g_path_get_dirname (target_file); filename = g_path_get_basename (target_file); if (!filename || filename[0] == '\0') { @@ -1348,6 +1395,7 @@ update_api_cb(evhtp_request_t *req, void *arg) filename, fsm->user, head_id, + mtime, &new_file_id, &error); if (rc < 0) { @@ -1388,6 +1436,8 @@ update_blks_api_cb(evhtp_request_t *req, void *arg) { RecvFSM *fsm = arg; char *target_file, *parent_dir = NULL, *filename = NULL, *size_str = NULL; + char *last_modify = NULL; + gint64 mtime = 0; const char *commitonly_str; GError *error = NULL; int error_code = -1; @@ -1411,6 +1461,11 @@ update_blks_api_cb(evhtp_request_t *req, void *arg) return; } + last_modify = g_hash_table_lookup (fsm->form_kvs, "last_modify"); + if (last_modify) { + mtime = rfc3339_to_timestamp (last_modify); + } + parent_dir = g_path_get_dirname (target_file); filename = g_path_get_basename (target_file); @@ -1461,6 +1516,7 @@ update_blks_api_cb(evhtp_request_t *req, void *arg) fsm->user, file_size, 1, + mtime, &new_file_id, &error); @@ -1637,6 +1693,8 @@ update_ajax_cb(evhtp_request_t *req, void *arg) { RecvFSM *fsm = arg; char *target_file, *parent_dir = NULL, *filename = NULL; + char *last_modify = NULL; + gint64 mtime = 0; const char *head_id = NULL; GError *error = NULL; int error_code = -1; @@ -1681,6 +1739,11 @@ update_ajax_cb(evhtp_request_t *req, void *arg) return; } + last_modify = g_hash_table_lookup (fsm->form_kvs, "last_modify"); + if (last_modify) { + mtime = rfc3339_to_timestamp (last_modify); + } + parent_dir = g_path_get_dirname (target_file); filename = g_path_get_basename (target_file); @@ -1716,6 +1779,7 @@ update_ajax_cb(evhtp_request_t *req, void *arg) filename, fsm->user, head_id, + mtime, &new_file_id, &error); diff --git a/tests/test_file_operation/test_upload_and_update.py b/tests/test_file_operation/test_upload_and_update.py index 0727ede6..4dd5fd7c 100644 --- a/tests/test_file_operation/test_upload_and_update.py +++ b/tests/test_file_operation/test_upload_and_update.py @@ -515,3 +515,47 @@ def test_api(repo): time.sleep(1) del_repo_files(repo.id) del_local_files() + +def test_ajax_mtime(repo): + create_test_file() + obj_id = '{"parent_dir":"/"}' + mtime = '2023-09-27T18:18:25+08:00' + + token = api.get_fileserver_access_token(repo.id, obj_id, 'upload', USER, False) + upload_url_base = 'http://127.0.0.1:8082/upload-aj/'+ token + m = MultipartEncoder( + fields={ + 'parent_dir': '/', + 'last_modify': mtime, + 'file': (file_name, open(file_path, 'rb'), 'application/octet-stream') + }) + response = requests.post(upload_url_base, + data = m, headers = {'Content-Type': m.content_type}) + assert_upload_response(response, False, False) + + dent = api.get_dirent_by_path(repo.id, '/' + file_name) + + assert dent.mtime == 1695809905 + +def test_api_mtime(repo): + create_test_file() + params = {'ret-json':'1'} + obj_id = '{"parent_dir":"/"}' + mtime = '2023-09-27T18:18:25+08:00' + + token = api.get_fileserver_access_token(repo.id, obj_id, 'upload', USER, False) + upload_url_base = 'http://127.0.0.1:8082/upload-api/' + token + m = MultipartEncoder( + fields={ + 'parent_dir': '/', + 'last_modify': mtime, + 'file': (file_name, open(file_path, 'rb'), 'application/octet-stream') + }) + response = requests.post(upload_url_base, params = params, + data = m, headers = {'Content-Type': m.content_type}) + assert_upload_response(response, False, False) + + dent = api.get_dirent_by_path(repo.id, '/' + file_name) + + assert dent.mtime == 1695809905 +