]> git.saurik.com Git - apt.git/blob - apt-pkg/install-progress.cc
prevent C++ locale number formatting in text APIs (try 2)
[apt.git] / apt-pkg / install-progress.cc
1 #include <config.h>
2
3 #include <apt-pkg/configuration.h>
4 #include <apt-pkg/fileutl.h>
5 #include <apt-pkg/strutl.h>
6 #include <apt-pkg/install-progress.h>
7
8 #include <signal.h>
9 #include <unistd.h>
10 #include <iostream>
11 #include <vector>
12 #include <sys/ioctl.h>
13 #include <fcntl.h>
14 #include <algorithm>
15 #include <stdio.h>
16 #include <sstream>
17
18 #include <apti18n.h>
19
20 namespace APT {
21 namespace Progress {
22
23 PackageManager::PackageManager() : d(NULL), percentage(0.0), last_reported_progress(-1) {}
24 PackageManager::~PackageManager() {}
25
26 /* Return a APT::Progress::PackageManager based on the global
27 * apt configuration (i.e. APT::Status-Fd and APT::Status-deb822-Fd)
28 */
29 PackageManager* PackageManagerProgressFactory()
30 {
31 // select the right progress
32 int status_fd = _config->FindI("APT::Status-Fd", -1);
33 int status_deb822_fd = _config->FindI("APT::Status-deb822-Fd", -1);
34
35 APT::Progress::PackageManager *progress = NULL;
36 if (status_deb822_fd > 0)
37 progress = new APT::Progress::PackageManagerProgressDeb822Fd(
38 status_deb822_fd);
39 else if (status_fd > 0)
40 progress = new APT::Progress::PackageManagerProgressFd(status_fd);
41 else if(_config->FindB("Dpkg::Progress-Fancy", false) == true)
42 progress = new APT::Progress::PackageManagerFancy();
43 else if (_config->FindB("Dpkg::Progress",
44 _config->FindB("DpkgPM::Progress", false)) == true)
45 progress = new APT::Progress::PackageManagerText();
46 else
47 progress = new APT::Progress::PackageManager();
48 return progress;
49 }
50
51 bool PackageManager::StatusChanged(std::string /*PackageName*/,
52 unsigned int StepsDone,
53 unsigned int TotalSteps,
54 std::string /*HumanReadableAction*/)
55 {
56 int reporting_steps = _config->FindI("DpkgPM::Reporting-Steps", 1);
57 percentage = StepsDone/(float)TotalSteps * 100.0;
58 strprintf(progress_str, _("Progress: [%3i%%]"), (int)percentage);
59
60 if(percentage < (last_reported_progress + reporting_steps))
61 return false;
62
63 return true;
64 }
65
66 PackageManagerProgressFd::PackageManagerProgressFd(int progress_fd)
67 : d(NULL), StepsDone(0), StepsTotal(1)
68 {
69 OutStatusFd = progress_fd;
70 }
71 PackageManagerProgressFd::~PackageManagerProgressFd() {}
72
73 void PackageManagerProgressFd::WriteToStatusFd(std::string s)
74 {
75 if(OutStatusFd <= 0)
76 return;
77 FileFd::Write(OutStatusFd, s.c_str(), s.size());
78 }
79
80 void PackageManagerProgressFd::StartDpkg()
81 {
82 if(OutStatusFd <= 0)
83 return;
84
85 // FIXME: use SetCloseExec here once it taught about throwing
86 // exceptions instead of doing _exit(100) on failure
87 fcntl(OutStatusFd,F_SETFD,FD_CLOEXEC);
88
89 // send status information that we are about to fork dpkg
90 std::string status;
91 strprintf(status, "pmstatus:dpkg-exec:%.4f:%s\n",
92 (StepsDone/float(StepsTotal)*100.0), _("Running dpkg"));
93 WriteToStatusFd(std::move(status));
94 }
95
96 APT_CONST void PackageManagerProgressFd::Stop()
97 {
98 }
99
100 void PackageManagerProgressFd::Error(std::string PackageName,
101 unsigned int StepsDone,
102 unsigned int TotalSteps,
103 std::string ErrorMessage)
104 {
105 std::string status;
106 strprintf(status, "pmerror:%s:%.4f:%s\n", PackageName.c_str(),
107 (StepsDone/float(TotalSteps)*100.0), ErrorMessage.c_str());
108 WriteToStatusFd(std::move(status));
109 }
110
111 void PackageManagerProgressFd::ConffilePrompt(std::string PackageName,
112 unsigned int StepsDone,
113 unsigned int TotalSteps,
114 std::string ConfMessage)
115 {
116 std::string status;
117 strprintf(status, "pmconffile:%s:%.4f:%s\n", PackageName.c_str(),
118 (StepsDone/float(TotalSteps)*100.0), ConfMessage.c_str());
119 WriteToStatusFd(std::move(status));
120 }
121
122
123 bool PackageManagerProgressFd::StatusChanged(std::string PackageName,
124 unsigned int xStepsDone,
125 unsigned int xTotalSteps,
126 std::string pkg_action)
127 {
128 StepsDone = xStepsDone;
129 StepsTotal = xTotalSteps;
130
131 // build the status str
132 std::string status;
133 strprintf(status, "pmstatus:%s:%.4f:%s\n", StringSplit(PackageName, ":")[0].c_str(),
134 (StepsDone/float(StepsTotal)*100.0), pkg_action.c_str());
135 WriteToStatusFd(std::move(status));
136
137 if(_config->FindB("Debug::APT::Progress::PackageManagerFd", false) == true)
138 std::cerr << "progress: " << PackageName << " " << xStepsDone
139 << " " << xTotalSteps << " " << pkg_action
140 << std::endl;
141
142
143 return true;
144 }
145
146
147 PackageManagerProgressDeb822Fd::PackageManagerProgressDeb822Fd(int progress_fd)
148 : d(NULL), StepsDone(0), StepsTotal(1)
149 {
150 OutStatusFd = progress_fd;
151 }
152 PackageManagerProgressDeb822Fd::~PackageManagerProgressDeb822Fd() {}
153
154 void PackageManagerProgressDeb822Fd::WriteToStatusFd(std::string s)
155 {
156 FileFd::Write(OutStatusFd, s.c_str(), s.size());
157 }
158
159 void PackageManagerProgressDeb822Fd::StartDpkg()
160 {
161 // FIXME: use SetCloseExec here once it taught about throwing
162 // exceptions instead of doing _exit(100) on failure
163 fcntl(OutStatusFd,F_SETFD,FD_CLOEXEC);
164
165 // send status information that we are about to fork dpkg
166 std::string status;
167 strprintf(status, "Status: %s\nPercent: %.4f\nMessage: %s\n\n", "progress",
168 (StepsDone/float(StepsTotal)*100.0), _("Running dpkg"));
169 WriteToStatusFd(std::move(status));
170 }
171
172 APT_CONST void PackageManagerProgressDeb822Fd::Stop()
173 {
174 }
175
176 void PackageManagerProgressDeb822Fd::Error(std::string PackageName,
177 unsigned int StepsDone,
178 unsigned int TotalSteps,
179 std::string ErrorMessage)
180 {
181 std::string status;
182 strprintf(status, "Status: %s\nPackage: %s\nPercent: %.4f\nMessage: %s\n\n", "Error",
183 PackageName.c_str(), (StepsDone/float(TotalSteps)*100.0), ErrorMessage.c_str());
184 WriteToStatusFd(std::move(status));
185 }
186
187 void PackageManagerProgressDeb822Fd::ConffilePrompt(std::string PackageName,
188 unsigned int StepsDone,
189 unsigned int TotalSteps,
190 std::string ConfMessage)
191 {
192 std::string status;
193 strprintf(status, "Status: %s\nPackage: %s\nPercent: %.4f\nMessage: %s\n\n", "ConfFile",
194 PackageName.c_str(), (StepsDone/float(TotalSteps)*100.0), ConfMessage.c_str());
195 WriteToStatusFd(std::move(status));
196 }
197
198
199 bool PackageManagerProgressDeb822Fd::StatusChanged(std::string PackageName,
200 unsigned int xStepsDone,
201 unsigned int xTotalSteps,
202 std::string message)
203 {
204 StepsDone = xStepsDone;
205 StepsTotal = xTotalSteps;
206
207 std::string status;
208 strprintf(status, "Status: %s\nPackage: %s\nPercent: %.4f\nMessage: %s\n\n", "progress",
209 PackageName.c_str(), (StepsDone/float(StepsTotal)*100.0), message.c_str());
210 WriteToStatusFd(std::move(status));
211 return true;
212 }
213
214
215 PackageManagerFancy::PackageManagerFancy()
216 : d(NULL), child_pty(-1)
217 {
218 // setup terminal size
219 old_SIGWINCH = signal(SIGWINCH, PackageManagerFancy::staticSIGWINCH);
220 instances.push_back(this);
221 }
222 std::vector<PackageManagerFancy*> PackageManagerFancy::instances;
223
224 PackageManagerFancy::~PackageManagerFancy()
225 {
226 instances.erase(find(instances.begin(), instances.end(), this));
227 signal(SIGWINCH, old_SIGWINCH);
228 }
229
230 void PackageManagerFancy::staticSIGWINCH(int signum)
231 {
232 std::vector<PackageManagerFancy *>::const_iterator I;
233 for(I = instances.begin(); I != instances.end(); ++I)
234 (*I)->HandleSIGWINCH(signum);
235 }
236
237 PackageManagerFancy::TermSize
238 PackageManagerFancy::GetTerminalSize()
239 {
240 struct winsize win;
241 PackageManagerFancy::TermSize s = { 0, 0 };
242
243 // FIXME: get from "child_pty" instead?
244 if(ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&win) != 0)
245 return s;
246
247 if(_config->FindB("Debug::InstallProgress::Fancy", false) == true)
248 std::cerr << "GetTerminalSize: " << win.ws_row << " x " << win.ws_col << std::endl;
249
250 s.rows = win.ws_row;
251 s.columns = win.ws_col;
252 return s;
253 }
254
255 void PackageManagerFancy::SetupTerminalScrollArea(int nr_rows)
256 {
257 if(_config->FindB("Debug::InstallProgress::Fancy", false) == true)
258 std::cerr << "SetupTerminalScrollArea: " << nr_rows << std::endl;
259
260 if (unlikely(nr_rows <= 1))
261 return;
262
263 // scroll down a bit to avoid visual glitch when the screen
264 // area shrinks by one row
265 std::cout << "\n";
266
267 // save cursor
268 std::cout << "\0337";
269
270 // set scroll region (this will place the cursor in the top left)
271 std::cout << "\033[0;" << std::to_string(nr_rows - 1) << "r";
272
273 // restore cursor but ensure its inside the scrolling area
274 std::cout << "\0338";
275 static const char *move_cursor_up = "\033[1A";
276 std::cout << move_cursor_up;
277
278 // ensure its flushed
279 std::flush(std::cout);
280
281 // setup tty size to ensure xterm/linux console are working properly too
282 // see bug #731738
283 struct winsize win;
284 if (ioctl(child_pty, TIOCGWINSZ, (char *)&win) != -1)
285 {
286 win.ws_row = nr_rows - 1;
287 ioctl(child_pty, TIOCSWINSZ, (char *)&win);
288 }
289 }
290
291 void PackageManagerFancy::HandleSIGWINCH(int)
292 {
293 int const nr_terminal_rows = GetTerminalSize().rows;
294 SetupTerminalScrollArea(nr_terminal_rows);
295 DrawStatusLine();
296 }
297
298 void PackageManagerFancy::Start(int a_child_pty)
299 {
300 child_pty = a_child_pty;
301 int const nr_terminal_rows = GetTerminalSize().rows;
302 SetupTerminalScrollArea(nr_terminal_rows);
303 }
304
305 void PackageManagerFancy::Stop()
306 {
307 int const nr_terminal_rows = GetTerminalSize().rows;
308 if (nr_terminal_rows > 0)
309 {
310 SetupTerminalScrollArea(nr_terminal_rows + 1);
311
312 // override the progress line (sledgehammer)
313 static const char* clear_screen_below_cursor = "\033[J";
314 std::cout << clear_screen_below_cursor;
315 std::flush(std::cout);
316 }
317 child_pty = -1;
318 }
319
320 std::string
321 PackageManagerFancy::GetTextProgressStr(float Percent, int OutputSize)
322 {
323 std::string output;
324 int i;
325
326 // should we raise a exception here instead?
327 if (Percent < 0.0 || Percent > 1.0 || OutputSize < 3)
328 return output;
329
330 int BarSize = OutputSize - 2; // bar without the leading "[" and trailing "]"
331 output += "[";
332 for(i=0; i < BarSize*Percent; i++)
333 output += "#";
334 for (/*nothing*/; i < BarSize; i++)
335 output += ".";
336 output += "]";
337 return output;
338 }
339
340 bool PackageManagerFancy::StatusChanged(std::string PackageName,
341 unsigned int StepsDone,
342 unsigned int TotalSteps,
343 std::string HumanReadableAction)
344 {
345 if (!PackageManager::StatusChanged(PackageName, StepsDone, TotalSteps,
346 HumanReadableAction))
347 return false;
348
349 return DrawStatusLine();
350 }
351 bool PackageManagerFancy::DrawStatusLine()
352 {
353 PackageManagerFancy::TermSize const size = GetTerminalSize();
354 if (unlikely(size.rows < 1 || size.columns < 1))
355 return false;
356
357 static std::string save_cursor = "\0337";
358 static std::string restore_cursor = "\0338";
359
360 // green
361 static std::string set_bg_color = DeQuoteString(
362 _config->Find("Dpkg::Progress-Fancy::Progress-fg", "%1b[42m"));
363 // black
364 static std::string set_fg_color = DeQuoteString(
365 _config->Find("Dpkg::Progress-Fancy::Progress-bg", "%1b[30m"));
366
367 static std::string restore_bg = "\033[49m";
368 static std::string restore_fg = "\033[39m";
369
370 std::cout << save_cursor
371 // move cursor position to last row
372 << "\033[" << std::to_string(size.rows) << ";0f"
373 << set_bg_color
374 << set_fg_color
375 << progress_str
376 << restore_bg
377 << restore_fg;
378 std::flush(std::cout);
379
380 // draw text progress bar
381 if (_config->FindB("Dpkg::Progress-Fancy::Progress-Bar", true))
382 {
383 int padding = 4;
384 float progressbar_size = size.columns - padding - progress_str.size();
385 float current_percent = percentage / 100.0;
386 std::cout << " "
387 << GetTextProgressStr(current_percent, progressbar_size)
388 << " ";
389 std::flush(std::cout);
390 }
391
392 // restore
393 std::cout << restore_cursor;
394 std::flush(std::cout);
395
396 last_reported_progress = percentage;
397
398 return true;
399 }
400
401 bool PackageManagerText::StatusChanged(std::string PackageName,
402 unsigned int StepsDone,
403 unsigned int TotalSteps,
404 std::string HumanReadableAction)
405 {
406 if (!PackageManager::StatusChanged(PackageName, StepsDone, TotalSteps, HumanReadableAction))
407 return false;
408
409 std::cout << progress_str << "\r\n";
410 std::flush(std::cout);
411
412 last_reported_progress = percentage;
413
414 return true;
415 }
416
417 PackageManagerText::PackageManagerText() : PackageManager(), d(NULL) {}
418 PackageManagerText::~PackageManagerText() {}
419
420
421
422
423 } // namespace progress
424 } // namespace apt