summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Kocoloski <kocolosk@apache.org>2009-10-29 03:19:45 +0000
committerAdam Kocoloski <kocolosk@apache.org>2009-10-29 03:19:45 +0000
commit97dc2ded847aec78ec2ac1f60eec583ae7111f6c (patch)
tree3495e8c09b579405fc0c353370faf07efb93b176
parent83ae3d22716bd27cac9aba134a32bfbe05f4301c (diff)
write config changes much faster - see COUCHDB-545. Thanks Gustavo Niemeyer
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@830832 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--THANKS1
-rw-r--r--src/couchdb/couch_config_writer.erl177
2 files changed, 49 insertions, 129 deletions
diff --git a/THANKS b/THANKS
index 28f0fbb7..324a1986 100644
--- a/THANKS
+++ b/THANKS
@@ -36,5 +36,6 @@ suggesting improvements or submitting changes. Some of these people are:
* Sven Helmberger <sven.helmberger@gmx.de>
* Dan Walters <dan@danwalters.net>
* Curt Arnold <carnold@apache.org>
+ * Gustavo Niemeyer
For a list of authors see the `AUTHORS` file.
diff --git a/src/couchdb/couch_config_writer.erl b/src/couchdb/couch_config_writer.erl
index 55e76a84..c8691d79 100644
--- a/src/couchdb/couch_config_writer.erl
+++ b/src/couchdb/couch_config_writer.erl
@@ -19,7 +19,6 @@
%% @see couch_config
-module(couch_config_writer).
--include("couch_db.hrl").
-export([save_to_file/2]).
@@ -27,134 +26,54 @@
%% Config::{{Section::string(), Option::string()}, Value::string()},
%% File::filename()) -> ok
%% @doc Saves a Section/Key/Value triple to the ini file File::filename()
-save_to_file({{Section, Option}, Value}, File) ->
+save_to_file({{Section, Key}, Value}, File) ->
+ {ok, OldFileContents} = file:read_file(File),
+ Lines = re:split(OldFileContents, "\r\n|\n|\r|\032", [{return, list}]),
- ?LOG_DEBUG("saving to file '~s', Config: '~p'", [File, {{Section, Option}, Value}]),
+ SectionLine = "[" ++ Section ++ "]",
+ {ok, Pattern} = re:compile(["^(", Key, "\\s*=)|\\[[a-zA-Z0-9\_-]*\\]"]),
- % open file and create a list of lines
- {ok, Stream} = file:read_file(File),
- OldFileContents = binary_to_list(Stream),
- Lines = re:split(OldFileContents, "\r\n|\n|\r|\032", [{return, list}]),
+ NewLines = process_file_lines(Lines, [], SectionLine, Pattern, Key, Value),
+ NewFileContents = reverse_and_add_newline(strip_empty_lines(NewLines), []),
+ ok = file:write_file(File, NewFileContents).
+
+
+process_file_lines([Section|Rest], SeenLines, Section, Pattern, Key, Value) ->
+ process_section_lines(Rest, [Section|SeenLines], Pattern, Key, Value);
+
+process_file_lines([Line|Rest], SeenLines, Section, Pattern, Key, Value) ->
+ process_file_lines(Rest, [Line|SeenLines], Section, Pattern, Key, Value);
+
+process_file_lines([], SeenLines, Section, _Pattern, Key, Value) ->
+ % Section wasn't found. Append it with the option here.
+ [Key ++ " = " ++ Value, Section, "" | strip_empty_lines(SeenLines)].
+
+
+process_section_lines([Line|Rest], SeenLines, Pattern, Key, Value) ->
+ case re:run(Line, Pattern, [{capture, all_but_first}]) of
+ nomatch -> % Found nothing interesting. Move on.
+ process_section_lines(Rest, [Line|SeenLines], Pattern, Key, Value);
+ {match, []} -> % Found another section. Append the option here.
+ lists:reverse(Rest) ++
+ [Line, "", Key ++ " = " ++ Value | strip_empty_lines(SeenLines)];
+ {match, _} -> % Found the option itself. Replace it.
+ lists:reverse(Rest) ++ [Key ++ " = " ++ Value | SeenLines]
+ end;
+
+process_section_lines([], SeenLines, _Pattern, Key, Value) ->
+ % Found end of file within the section. Append the option here.
+ [Key ++ " = " ++ Value | strip_empty_lines(SeenLines)].
+
+
+reverse_and_add_newline([Line|Rest], Content) ->
+ reverse_and_add_newline(Rest, [Line, "\n", Content]);
+
+reverse_and_add_newline([], Content) ->
+ Content.
+
+
+strip_empty_lines(["" | Rest]) ->
+ strip_empty_lines(Rest);
- % prepare input variables
- SectionName = "[" ++ Section ++ "]",
- OptionList = Option,
-
- % produce the contents for the config file
- NewFileContents =
- case {NewFileContents2, DoneOptions} = save_loop({{SectionName, OptionList}, Value}, Lines, "", "", []) of
- % we didn't change anything, that means we couldn't find a matching
- % [ini section] in which case we just append a new one.
- {OldFileContents, DoneOptions} ->
- % but only if we haven't actually written anything.
- case lists:member(OptionList, DoneOptions) of
- true -> OldFileContents;
- _ -> append_new_ini_section({{SectionName, OptionList}, Value}, OldFileContents)
- end;
- _ ->
- NewFileContents2
- end,
-
- case file:write_file(File, list_to_binary(NewFileContents)) of
- ok -> ok;
- _Else -> throw({permissions_error, <<"Config file is not writeable.">>})
- end,
- ok.
-
-%% @doc Iterates over the lines of an ini file and replaces or adds a new
-%% configuration directive.
-save_loop({{Section, Option}, Value}, [Line|Rest], OldCurrentSection, Contents, DoneOptions) ->
-
- % if we find a new [ini section] (Section), save that for reference
- NewCurrentSection = parse_module(Line, OldCurrentSection),
- % if the current Section is the one we want to change, try to match
- % each line with the Option
- NewContents =
- case NewCurrentSection of
- Section ->
- case OldCurrentSection of
- NewCurrentSection -> % we already were in [Section]
- case lists:member(Option, DoneOptions) of
- true -> % we already replaced Option, do nothing
- DoneOptions2 = DoneOptions,
- Line;
- _ -> % we haven't written our Option yet
- case parse_variable(Line, Option, Value) of
- nomatch ->
- DoneOptions2 = DoneOptions,
- Line;
- NewLine ->
- DoneOptions2 = [Option|DoneOptions],
- NewLine
- end
- end;
- _ -> % we got into a new [section]
- {NewLine, DoneOptions2} = append_var_to_section(
- {{Section, Option}, Value},
- Line,
- OldCurrentSection,
- DoneOptions),
- NewLine
- end;
- _ -> % we are reading [NewCurrentSection]
- {NewLine, DoneOptions2} = append_var_to_section(
- {{Section, Option}, Value},
- Line,
- OldCurrentSection,
- DoneOptions),
- NewLine
- end,
- % clumsy way to only append a newline character if the line is not empty. We need this to
- % avoid having a newline inserted at the top of the target file each time we save it.
- Contents2 = case Contents of "" -> ""; _ -> Contents ++ "\n" end,
- % go to next line
- save_loop({{Section, Option}, Value}, Rest, NewCurrentSection, Contents2 ++ NewContents, DoneOptions2);
-
-save_loop({{Section, Option}, Value}, [], OldSection, NewFileContents, DoneOptions) ->
- case lists:member(Option, DoneOptions) of
- % append Deferred Option
- false when Section == OldSection ->
- {NewFileContents ++ "\n" ++ Option ++ " = " ++ Value ++ "\n", DoneOptions};
- % we're out of new lines, just return the new file's contents
- _ -> {NewFileContents, DoneOptions}
- end.
-
-append_new_ini_section({{SectionName, Option}, Value}, OldFileContents) ->
- OldFileContents ++ "\n" ++ SectionName ++ "\n" ++ Option ++ " = " ++ Value ++ "\n".
-
-append_var_to_section({{Section, Option}, Value}, Line, OldCurrentSection, DoneOptions) ->
- case OldCurrentSection of
- Section -> % append Option to Section
- case lists:member(Option, DoneOptions) of
- false ->
- {Option ++ " = " ++ Value ++ "\n\n" ++ Line, [Option|DoneOptions]};
- _ ->
- {Line, DoneOptions}
- end;
- _ ->
- {Line, DoneOptions}
- end.
-
-%% @spec parse_module(Line::string(), OldSection::string()) -> string()
-%% @doc Tries to match a line against a pattern specifying a ini module or
-%% section ("[Section]"). Returns OldSection if no match is found.
-parse_module(Line, OldSection) ->
- case re:run(Line, "^\\[([a-zA-Z0-9\_-]*)\\]$", [{capture, first}]) of
- nomatch ->
- OldSection;
- {match, [{Start, Length}]} ->
- string:substr(Line, Start+1, Length)
- end.
-
-%% @spec parse_variable(Line::string(), Option::string(), Value::string()) ->
-%% string() | nomatch
-%% @doc Tries to match a variable assignment in Line. Returns nomatch if the
-%% Option is not found. Returns a new line composed of the Option and
-%% Value otherwise.
-parse_variable(Line, Option, Value) ->
- case re:run(Line, "^" ++ Option ++ "\s?=", [{capture, none}]) of
- nomatch ->
- nomatch;
- match ->
- Option ++ " = " ++ Value
- end.
+strip_empty_lines(All) ->
+ All.