author | Lars Hjemli <hjemli@gmail.com> | 2008-11-29 15:46:37 (UTC) |
---|---|---|
committer | Lars Hjemli <hjemli@gmail.com> | 2008-11-29 15:46:37 (UTC) |
commit | 8813170390f3c3a0f4743afbc92ede42953fa3b0 (patch) (unidiff) | |
tree | 39305350baee1eb564aae00294634bbe544983d3 /ui-repolist.c | |
parent | 54272e60965ec6a98b49cbf67d72a4b1f5adc55b (diff) | |
download | cgit-8813170390f3c3a0f4743afbc92ede42953fa3b0.zip cgit-8813170390f3c3a0f4743afbc92ede42953fa3b0.tar.gz cgit-8813170390f3c3a0f4743afbc92ede42953fa3b0.tar.bz2 |
ui-repolist: implement lazy caching of repo->mtime
When sorting the list of repositories by their last modification time,
cgit would (in the worst case) invoke fstat(3) four times and open(3)
twice for each callback from qsort(3). This obviously scales very badly.
Now, the calculated modtime for each repo is saved in repo->mtime, thus
keeping the number of stat/open invocations identical for sorted and
unsorted repo-listings.
Signed-off-by: Lars Hjemli <hjemli@gmail.com>
-rw-r--r-- | ui-repolist.c | 16 |
1 files changed, 12 insertions, 4 deletions
diff --git a/ui-repolist.c b/ui-repolist.c index cf27cb3..aa743bf 100644 --- a/ui-repolist.c +++ b/ui-repolist.c | |||
@@ -1,266 +1,274 @@ | |||
1 | /* ui-repolist.c: functions for generating the repolist page | 1 | /* ui-repolist.c: functions for generating the repolist page |
2 | * | 2 | * |
3 | * Copyright (C) 2006 Lars Hjemli | 3 | * Copyright (C) 2006 Lars Hjemli |
4 | * | 4 | * |
5 | * Licensed under GNU General Public License v2 | 5 | * Licensed under GNU General Public License v2 |
6 | * (see COPYING for full license text) | 6 | * (see COPYING for full license text) |
7 | */ | 7 | */ |
8 | 8 | ||
9 | #include <time.h> | 9 | #include <time.h> |
10 | 10 | ||
11 | #include "cgit.h" | 11 | #include "cgit.h" |
12 | #include "html.h" | 12 | #include "html.h" |
13 | #include "ui-shared.h" | 13 | #include "ui-shared.h" |
14 | 14 | ||
15 | time_t read_agefile(char *path) | 15 | time_t read_agefile(char *path) |
16 | { | 16 | { |
17 | FILE *f; | 17 | FILE *f; |
18 | static char buf[64], buf2[64]; | 18 | static char buf[64], buf2[64]; |
19 | 19 | ||
20 | if (!(f = fopen(path, "r"))) | 20 | if (!(f = fopen(path, "r"))) |
21 | return -1; | 21 | return -1; |
22 | if (fgets(buf, sizeof(buf), f) == NULL) | 22 | if (fgets(buf, sizeof(buf), f) == NULL) |
23 | return -1; | 23 | return -1; |
24 | fclose(f); | 24 | fclose(f); |
25 | if (parse_date(buf, buf2, sizeof(buf2))) | 25 | if (parse_date(buf, buf2, sizeof(buf2))) |
26 | return strtoul(buf2, NULL, 10); | 26 | return strtoul(buf2, NULL, 10); |
27 | else | 27 | else |
28 | return 0; | 28 | return 0; |
29 | } | 29 | } |
30 | 30 | ||
31 | static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) | 31 | static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) |
32 | { | 32 | { |
33 | char *path; | 33 | char *path; |
34 | struct stat s; | 34 | struct stat s; |
35 | struct cgit_repo *r = (struct cgit_repo *)repo; | ||
35 | 36 | ||
37 | if (repo->mtime != -1) { | ||
38 | *mtime = repo->mtime; | ||
39 | return 1; | ||
40 | } | ||
36 | path = fmt("%s/%s", repo->path, ctx.cfg.agefile); | 41 | path = fmt("%s/%s", repo->path, ctx.cfg.agefile); |
37 | if (stat(path, &s) == 0) { | 42 | if (stat(path, &s) == 0) { |
38 | *mtime = read_agefile(path); | 43 | *mtime = read_agefile(path); |
44 | r->mtime = *mtime; | ||
39 | return 1; | 45 | return 1; |
40 | } | 46 | } |
41 | 47 | ||
42 | path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); | 48 | path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); |
43 | if (stat(path, &s) == 0) { | 49 | if (stat(path, &s) == 0) |
44 | *mtime = s.st_mtime; | 50 | *mtime = s.st_mtime; |
45 | return 1; | 51 | else |
46 | } | 52 | *mtime = 0; |
47 | return 0; | 53 | |
54 | r->mtime = *mtime; | ||
55 | return (r->mtime != 0); | ||
48 | } | 56 | } |
49 | 57 | ||
50 | static void print_modtime(struct cgit_repo *repo) | 58 | static void print_modtime(struct cgit_repo *repo) |
51 | { | 59 | { |
52 | time_t t; | 60 | time_t t; |
53 | if (get_repo_modtime(repo, &t)) | 61 | if (get_repo_modtime(repo, &t)) |
54 | cgit_print_age(t, -1, NULL); | 62 | cgit_print_age(t, -1, NULL); |
55 | } | 63 | } |
56 | 64 | ||
57 | int is_match(struct cgit_repo *repo) | 65 | int is_match(struct cgit_repo *repo) |
58 | { | 66 | { |
59 | if (!ctx.qry.search) | 67 | if (!ctx.qry.search) |
60 | return 1; | 68 | return 1; |
61 | if (repo->url && strcasestr(repo->url, ctx.qry.search)) | 69 | if (repo->url && strcasestr(repo->url, ctx.qry.search)) |
62 | return 1; | 70 | return 1; |
63 | if (repo->name && strcasestr(repo->name, ctx.qry.search)) | 71 | if (repo->name && strcasestr(repo->name, ctx.qry.search)) |
64 | return 1; | 72 | return 1; |
65 | if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) | 73 | if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) |
66 | return 1; | 74 | return 1; |
67 | if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) | 75 | if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) |
68 | return 1; | 76 | return 1; |
69 | return 0; | 77 | return 0; |
70 | } | 78 | } |
71 | 79 | ||
72 | int is_in_url(struct cgit_repo *repo) | 80 | int is_in_url(struct cgit_repo *repo) |
73 | { | 81 | { |
74 | if (!ctx.qry.url) | 82 | if (!ctx.qry.url) |
75 | return 1; | 83 | return 1; |
76 | if (repo->url && !prefixcmp(repo->url, ctx.qry.url)) | 84 | if (repo->url && !prefixcmp(repo->url, ctx.qry.url)) |
77 | return 1; | 85 | return 1; |
78 | return 0; | 86 | return 0; |
79 | } | 87 | } |
80 | 88 | ||
81 | void print_sort_header(const char *title, const char *sort) | 89 | void print_sort_header(const char *title, const char *sort) |
82 | { | 90 | { |
83 | htmlf("<th class='left'><a href='./?s=%s", sort); | 91 | htmlf("<th class='left'><a href='./?s=%s", sort); |
84 | if (ctx.qry.search) { | 92 | if (ctx.qry.search) { |
85 | html("&q="); | 93 | html("&q="); |
86 | html_url_arg(ctx.qry.search); | 94 | html_url_arg(ctx.qry.search); |
87 | } | 95 | } |
88 | htmlf("'>%s</a></th>", title); | 96 | htmlf("'>%s</a></th>", title); |
89 | } | 97 | } |
90 | 98 | ||
91 | void print_header(int columns) | 99 | void print_header(int columns) |
92 | { | 100 | { |
93 | html("<tr class='nohover'>"); | 101 | html("<tr class='nohover'>"); |
94 | print_sort_header("Name", "name"); | 102 | print_sort_header("Name", "name"); |
95 | print_sort_header("Description", "desc"); | 103 | print_sort_header("Description", "desc"); |
96 | print_sort_header("Owner", "owner"); | 104 | print_sort_header("Owner", "owner"); |
97 | print_sort_header("Idle", "idle"); | 105 | print_sort_header("Idle", "idle"); |
98 | if (ctx.cfg.enable_index_links) | 106 | if (ctx.cfg.enable_index_links) |
99 | html("<th class='left'>Links</th>"); | 107 | html("<th class='left'>Links</th>"); |
100 | html("</tr>\n"); | 108 | html("</tr>\n"); |
101 | } | 109 | } |
102 | 110 | ||
103 | 111 | ||
104 | void print_pager(int items, int pagelen, char *search) | 112 | void print_pager(int items, int pagelen, char *search) |
105 | { | 113 | { |
106 | int i; | 114 | int i; |
107 | html("<div class='pager'>"); | 115 | html("<div class='pager'>"); |
108 | for(i = 0; i * pagelen < items; i++) | 116 | for(i = 0; i * pagelen < items; i++) |
109 | cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL, | 117 | cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL, |
110 | search, i * pagelen); | 118 | search, i * pagelen); |
111 | html("</div>"); | 119 | html("</div>"); |
112 | } | 120 | } |
113 | 121 | ||
114 | static int cmp(const char *s1, const char *s2) | 122 | static int cmp(const char *s1, const char *s2) |
115 | { | 123 | { |
116 | if (s1 && s2) | 124 | if (s1 && s2) |
117 | return strcmp(s1, s2); | 125 | return strcmp(s1, s2); |
118 | if (s1 && !s2) | 126 | if (s1 && !s2) |
119 | return -1; | 127 | return -1; |
120 | if (s2 && !s1) | 128 | if (s2 && !s1) |
121 | return 1; | 129 | return 1; |
122 | return 0; | 130 | return 0; |
123 | } | 131 | } |
124 | 132 | ||
125 | static int sort_name(const void *a, const void *b) | 133 | static int sort_name(const void *a, const void *b) |
126 | { | 134 | { |
127 | const struct cgit_repo *r1 = a; | 135 | const struct cgit_repo *r1 = a; |
128 | const struct cgit_repo *r2 = b; | 136 | const struct cgit_repo *r2 = b; |
129 | 137 | ||
130 | return cmp(r1->name, r2->name); | 138 | return cmp(r1->name, r2->name); |
131 | } | 139 | } |
132 | 140 | ||
133 | static int sort_desc(const void *a, const void *b) | 141 | static int sort_desc(const void *a, const void *b) |
134 | { | 142 | { |
135 | const struct cgit_repo *r1 = a; | 143 | const struct cgit_repo *r1 = a; |
136 | const struct cgit_repo *r2 = b; | 144 | const struct cgit_repo *r2 = b; |
137 | 145 | ||
138 | return cmp(r1->desc, r2->desc); | 146 | return cmp(r1->desc, r2->desc); |
139 | } | 147 | } |
140 | 148 | ||
141 | static int sort_owner(const void *a, const void *b) | 149 | static int sort_owner(const void *a, const void *b) |
142 | { | 150 | { |
143 | const struct cgit_repo *r1 = a; | 151 | const struct cgit_repo *r1 = a; |
144 | const struct cgit_repo *r2 = b; | 152 | const struct cgit_repo *r2 = b; |
145 | 153 | ||
146 | return cmp(r1->owner, r2->owner); | 154 | return cmp(r1->owner, r2->owner); |
147 | } | 155 | } |
148 | 156 | ||
149 | static int sort_idle(const void *a, const void *b) | 157 | static int sort_idle(const void *a, const void *b) |
150 | { | 158 | { |
151 | const struct cgit_repo *r1 = a; | 159 | const struct cgit_repo *r1 = a; |
152 | const struct cgit_repo *r2 = b; | 160 | const struct cgit_repo *r2 = b; |
153 | time_t t1, t2; | 161 | time_t t1, t2; |
154 | 162 | ||
155 | t1 = t2 = 0; | 163 | t1 = t2 = 0; |
156 | get_repo_modtime(r1, &t1); | 164 | get_repo_modtime(r1, &t1); |
157 | get_repo_modtime(r2, &t2); | 165 | get_repo_modtime(r2, &t2); |
158 | return t2 - t1; | 166 | return t2 - t1; |
159 | } | 167 | } |
160 | 168 | ||
161 | struct sortcolumn { | 169 | struct sortcolumn { |
162 | const char *name; | 170 | const char *name; |
163 | int (*fn)(const void *a, const void *b); | 171 | int (*fn)(const void *a, const void *b); |
164 | }; | 172 | }; |
165 | 173 | ||
166 | struct sortcolumn sortcolumn[] = { | 174 | struct sortcolumn sortcolumn[] = { |
167 | {"name", sort_name}, | 175 | {"name", sort_name}, |
168 | {"desc", sort_desc}, | 176 | {"desc", sort_desc}, |
169 | {"owner", sort_owner}, | 177 | {"owner", sort_owner}, |
170 | {"idle", sort_idle}, | 178 | {"idle", sort_idle}, |
171 | {NULL, NULL} | 179 | {NULL, NULL} |
172 | }; | 180 | }; |
173 | 181 | ||
174 | int sort_repolist(char *field) | 182 | int sort_repolist(char *field) |
175 | { | 183 | { |
176 | struct sortcolumn *column; | 184 | struct sortcolumn *column; |
177 | 185 | ||
178 | for (column = &sortcolumn[0]; column->name; column++) { | 186 | for (column = &sortcolumn[0]; column->name; column++) { |
179 | if (strcmp(field, column->name)) | 187 | if (strcmp(field, column->name)) |
180 | continue; | 188 | continue; |
181 | qsort(cgit_repolist.repos, cgit_repolist.count, | 189 | qsort(cgit_repolist.repos, cgit_repolist.count, |
182 | sizeof(struct cgit_repo), column->fn); | 190 | sizeof(struct cgit_repo), column->fn); |
183 | return 1; | 191 | return 1; |
184 | } | 192 | } |
185 | return 0; | 193 | return 0; |
186 | } | 194 | } |
187 | 195 | ||
188 | 196 | ||
189 | void cgit_print_repolist() | 197 | void cgit_print_repolist() |
190 | { | 198 | { |
191 | int i, columns = 4, hits = 0, header = 0; | 199 | int i, columns = 4, hits = 0, header = 0; |
192 | char *last_group = NULL; | 200 | char *last_group = NULL; |
193 | int sorted = 0; | 201 | int sorted = 0; |
194 | 202 | ||
195 | if (ctx.cfg.enable_index_links) | 203 | if (ctx.cfg.enable_index_links) |
196 | columns++; | 204 | columns++; |
197 | 205 | ||
198 | ctx.page.title = ctx.cfg.root_title; | 206 | ctx.page.title = ctx.cfg.root_title; |
199 | cgit_print_http_headers(&ctx); | 207 | cgit_print_http_headers(&ctx); |
200 | cgit_print_docstart(&ctx); | 208 | cgit_print_docstart(&ctx); |
201 | cgit_print_pageheader(&ctx); | 209 | cgit_print_pageheader(&ctx); |
202 | 210 | ||
203 | if (ctx.cfg.index_header) | 211 | if (ctx.cfg.index_header) |
204 | html_include(ctx.cfg.index_header); | 212 | html_include(ctx.cfg.index_header); |
205 | 213 | ||
206 | if(ctx.qry.sort) | 214 | if(ctx.qry.sort) |
207 | sorted = sort_repolist(ctx.qry.sort); | 215 | sorted = sort_repolist(ctx.qry.sort); |
208 | 216 | ||
209 | html("<table summary='repository list' class='list nowrap'>"); | 217 | html("<table summary='repository list' class='list nowrap'>"); |
210 | for (i=0; i<cgit_repolist.count; i++) { | 218 | for (i=0; i<cgit_repolist.count; i++) { |
211 | ctx.repo = &cgit_repolist.repos[i]; | 219 | ctx.repo = &cgit_repolist.repos[i]; |
212 | if (!(is_match(ctx.repo) && is_in_url(ctx.repo))) | 220 | if (!(is_match(ctx.repo) && is_in_url(ctx.repo))) |
213 | continue; | 221 | continue; |
214 | hits++; | 222 | hits++; |
215 | if (hits <= ctx.qry.ofs) | 223 | if (hits <= ctx.qry.ofs) |
216 | continue; | 224 | continue; |
217 | if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) | 225 | if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) |
218 | continue; | 226 | continue; |
219 | if (!header++) | 227 | if (!header++) |
220 | print_header(columns); | 228 | print_header(columns); |
221 | if (!sorted && | 229 | if (!sorted && |
222 | ((last_group == NULL && ctx.repo->group != NULL) || | 230 | ((last_group == NULL && ctx.repo->group != NULL) || |
223 | (last_group != NULL && ctx.repo->group == NULL) || | 231 | (last_group != NULL && ctx.repo->group == NULL) || |
224 | (last_group != NULL && ctx.repo->group != NULL && | 232 | (last_group != NULL && ctx.repo->group != NULL && |
225 | strcmp(ctx.repo->group, last_group)))) { | 233 | strcmp(ctx.repo->group, last_group)))) { |
226 | htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", | 234 | htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", |
227 | columns); | 235 | columns); |
228 | html_txt(ctx.repo->group); | 236 | html_txt(ctx.repo->group); |
229 | html("</td></tr>"); | 237 | html("</td></tr>"); |
230 | last_group = ctx.repo->group; | 238 | last_group = ctx.repo->group; |
231 | } | 239 | } |
232 | htmlf("<tr><td class='%s'>", | 240 | htmlf("<tr><td class='%s'>", |
233 | !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); | 241 | !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); |
234 | cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); | 242 | cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); |
235 | html("</td><td>"); | 243 | html("</td><td>"); |
236 | html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); | 244 | html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); |
237 | html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc); | 245 | html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc); |
238 | html_link_close(); | 246 | html_link_close(); |
239 | html("</td><td>"); | 247 | html("</td><td>"); |
240 | html_txt(ctx.repo->owner); | 248 | html_txt(ctx.repo->owner); |
241 | html("</td><td>"); | 249 | html("</td><td>"); |
242 | print_modtime(ctx.repo); | 250 | print_modtime(ctx.repo); |
243 | html("</td>"); | 251 | html("</td>"); |
244 | if (ctx.cfg.enable_index_links) { | 252 | if (ctx.cfg.enable_index_links) { |
245 | html("<td>"); | 253 | html("<td>"); |
246 | cgit_summary_link("summary", NULL, "button", NULL); | 254 | cgit_summary_link("summary", NULL, "button", NULL); |
247 | cgit_log_link("log", NULL, "button", NULL, NULL, NULL, | 255 | cgit_log_link("log", NULL, "button", NULL, NULL, NULL, |
248 | 0, NULL, NULL); | 256 | 0, NULL, NULL); |
249 | cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); | 257 | cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); |
250 | html("</td>"); | 258 | html("</td>"); |
251 | } | 259 | } |
252 | html("</tr>\n"); | 260 | html("</tr>\n"); |
253 | } | 261 | } |
254 | html("</table>"); | 262 | html("</table>"); |
255 | if (!hits) | 263 | if (!hits) |
256 | cgit_print_error("No repositories found"); | 264 | cgit_print_error("No repositories found"); |
257 | else if (hits > ctx.cfg.max_repo_count) | 265 | else if (hits > ctx.cfg.max_repo_count) |
258 | print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search); | 266 | print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search); |
259 | cgit_print_docend(); | 267 | cgit_print_docend(); |
260 | } | 268 | } |
261 | 269 | ||
262 | void cgit_print_site_readme() | 270 | void cgit_print_site_readme() |
263 | { | 271 | { |
264 | if (ctx.cfg.root_readme) | 272 | if (ctx.cfg.root_readme) |
265 | html_include(ctx.cfg.root_readme); | 273 | html_include(ctx.cfg.root_readme); |
266 | } | 274 | } |