Move all functions related to file deletion in `couch_file` module
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/f8432a1d Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/f8432a1d Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/f8432a1d Branch: refs/heads/master Commit: f8432a1d51c61da52fd8cf8a4b9a41c093df5d02 Parents: bc3ecda Author: Eric Avdey <[email protected]> Authored: Fri Apr 22 11:09:59 2016 -0300 Committer: Eric Avdey <[email protected]> Committed: Thu Apr 28 13:09:35 2016 -0300 ---------------------------------------------------------------------- src/couch_file.erl | 61 +++++++++++++++++++++++++++++++++++++++++- src/couch_server.erl | 27 ++----------------- test/couch_file_tests.erl | 47 ++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 26 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/f8432a1d/src/couch_file.erl ---------------------------------------------------------------------- diff --git a/src/couch_file.erl b/src/couch_file.erl index f6c5a4f..9a9c070 100644 --- a/src/couch_file.erl +++ b/src/couch_file.erl @@ -221,8 +221,16 @@ close(Fd) -> delete(RootDir, Filepath) -> delete(RootDir, Filepath, true). +delete(RootDir, FullFilePath, Async) -> + RenameOnDelete = config:get_boolean("couchdb", "rename_on_delete", false), + case RenameOnDelete of + true -> + rename_file(FullFilePath); + false -> + delete_file(RootDir, FullFilePath, Async) + end. -delete(RootDir, Filepath, Async) -> +delete_file(RootDir, Filepath, Async) -> DelFile = filename:join([RootDir,".delete", ?b2l(couch_uuids:random())]), case file:rename(Filepath, DelFile) of ok -> @@ -236,6 +244,17 @@ delete(RootDir, Filepath, Async) -> Error end. +rename_file(Original) -> + DeletedFileName = deleted_filename(Original), + file:rename(Original, DeletedFileName). + +deleted_filename(Original) -> + {{Y, Mon, D}, {H, Min, S}} = calendar:universal_time(), + Suffix = lists:flatten( + io_lib:format(".~w~2.10.0B~2.10.0B." + ++ "~2.10.0B~2.10.0B~2.10.0B.deleted" + ++ filename:extension(Original), [Y, Mon, D, H, Min, S])), + filename:rootname(Original) ++ Suffix. nuke_dir(RootDelDir, Dir) -> FoldFun = fun(File) -> @@ -610,3 +629,43 @@ process_info(Pid) -> {couch_file_fd, {Fd, InitialName}} -> {Fd, InitialName} end. + + +-ifdef(TEST). +-include_lib("couch/include/couch_eunit.hrl"). + +deleted_filename_test_() -> + DbNames = ["dbname", "db.name", "user/dbname"], + Fixtures = make_filename_fixtures(DbNames), + lists:map(fun(Fixture) -> + should_create_proper_deleted_filename(Fixture) + end, Fixtures). + +should_create_proper_deleted_filename(Before) -> + {Before, + ?_test(begin + BeforeExtension = filename:extension(Before), + BeforeBasename = filename:basename(Before, BeforeExtension), + Re = "^" ++ BeforeBasename ++ "\.[0-9]{8}\.[0-9]{6}\.deleted\..*$", + After = deleted_filename(Before), + ?assertEqual(match, + re:run(filename:basename(After), Re, [{capture, none}])), + ?assertEqual(BeforeExtension, filename:extension(After)) + end)}. + +make_filename_fixtures(DbNames) -> + Formats = [ + "~s.couch", + ".~s_design/mrview/3133e28517e89a3e11435dd5ac4ad85a.view", + "shards/00000000-1fffffff/~s.1458336317.couch", + ".shards/00000000-1fffffff/~s.1458336317_design", + ".shards/00000000-1fffffff/~s.1458336317_design" + "/mrview/3133e28517e89a3e11435dd5ac4ad85a.view" + ], + lists:flatmap(fun(DbName) -> + lists:map(fun(Format) -> + filename:join("/srv/data", io_lib:format(Format, [DbName])) + end, Formats) + end, DbNames). + +-endif. http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/f8432a1d/src/couch_server.erl ---------------------------------------------------------------------- diff --git a/src/couch_server.erl b/src/couch_server.erl index dc50e82..f280eca 100644 --- a/src/couch_server.erl +++ b/src/couch_server.erl @@ -25,8 +25,6 @@ % config_listener api -export([handle_config_change/5, handle_config_terminate/3]). --export([delete_file/3]). - -include_lib("couch/include/couch_db.hrl"). -define(MAX_DBS_OPEN, 100). @@ -461,7 +459,8 @@ handle_call({delete, DbName, Options}, _From, Server) -> couch_db_plugin:on_delete(DbName, Options), - case delete_file(Server#server.root_dir, FullFilepath, Options) of + Async = not lists:member(sync, Options), + case couch_file:delete(Server#server.root_dir, FullFilepath, Async) of ok -> couch_event:notify(DbName, deleted), {reply, ok, Server2}; @@ -538,25 +537,3 @@ db_closed(Server, Options) -> false -> Server#server{dbs_open=Server#server.dbs_open - 1}; true -> Server end. - -delete_file(RootDir, FullFilePath, Options) -> - Async = not lists:member(sync, Options), - RenameOnDelete = config:get_boolean("couchdb", "rename_on_delete", false), - case RenameOnDelete of - true -> - rename_on_delete(FullFilePath); - false -> - couch_file:delete(RootDir, FullFilePath, Async) - end. - -rename_on_delete(Original) -> - DeletedFileName = deleted_filename(Original), - file:rename(Original, DeletedFileName). - -deleted_filename(Original) -> - {{Y,Mon,D}, {H,Min,S}} = calendar:universal_time(), - Suffix = lists:flatten( - io_lib:format(".~w~2.10.0B~2.10.0B." - ++ "~2.10.0B~2.10.0B~2.10.0B.deleted" - ++ filename:extension(Original), [Y,Mon,D,H,Min,S])), - filename:rootname(Original) ++ Suffix. http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/f8432a1d/test/couch_file_tests.erl ---------------------------------------------------------------------- diff --git a/test/couch_file_tests.erl b/test/couch_file_tests.erl index 4d0bbac..df122b0 100644 --- a/test/couch_file_tests.erl +++ b/test/couch_file_tests.erl @@ -274,3 +274,50 @@ write_random_data(Fd, N) -> Term = lists:nth(random:uniform(4) + 1, Choices), {ok, _, _} = couch_file:append_term(Fd, Term), write_random_data(Fd, N - 1). + + +delete_test_() -> + { + "File delete tests", + { + foreach, + fun() -> + meck:new(config, [passthrough]), + File = ?tempfile() ++ ".couch", + RootDir = filename:dirname(File), + ok = couch_file:init_delete_dir(RootDir), + ok = file:write_file(File, <<>>), + {RootDir, File} + end, + fun({_, File}) -> + meck:unload(config), + file:delete(File) + end, + [ + fun(Cfg) -> + {"rename_on_delete = false", + make_delete_test_case(Cfg, false)} + end, + fun(Cfg) -> + {"rename_on_delete = true", + make_delete_test_case(Cfg, true)} + end + ] + } + }. + + +make_delete_test_case({RootDir, File}, RenameOnDelete) -> + meck:expect(config, get_boolean, fun + ("couchdb", "rename_on_delete", _) -> RenameOnDelete + end), + FileExistsBefore = filelib:is_regular(File), + couch_file:delete(RootDir, File, false), + FileExistsAfter = filelib:is_regular(File), + RenamedFiles = filelib:wildcard(filename:rootname(File) ++ "*.deleted.*"), + ExpectRenamedCount = if RenameOnDelete -> 1; true -> 0 end, + [ + ?_assert(FileExistsBefore), + ?_assertNot(FileExistsAfter), + ?_assertEqual(ExpectRenamedCount, length(RenamedFiles)) + ].
