1 /*
2 * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de>
3 *
4 * All rights reserved. Distributed under the terms of the MIT License.
5 */
6
7 #include "DownloadProgressView.h"
8
9 #include <stdio.h>
10
11 #include <Alert.h>
12 #include <Application.h>
13 #include <Bitmap.h>
14 #include <Button.h>
15 #include <Catalog.h>
16 #include <Clipboard.h>
17 #include <Directory.h>
18 #include <DateTimeFormat.h>
19 #include <DurationFormat.h>
20 #include <Entry.h>
21 #include <FindDirectory.h>
22 #include <GroupLayoutBuilder.h>
23 #include <Locale.h>
24 #include <MenuItem.h>
25 #include <NodeInfo.h>
26 #include <NodeMonitor.h>
27 #include <Notification.h>
28 #include <PopUpMenu.h>
29 #include <Roster.h>
30 #include <SpaceLayoutItem.h>
31 #include <StatusBar.h>
32 #include <StringView.h>
33 #include <TimeFormat.h>
34
35 #include "BrowserWindow.h"
36 #include "WebDownload.h"
37 #include "WebPage.h"
38 #include "StringForSize.h"
39
40
41 #undef B_TRANSLATION_CONTEXT
42 #define B_TRANSLATION_CONTEXT "Download Window"
43
44 enum {
45 OPEN_DOWNLOAD = 'opdn',
46 RESTART_DOWNLOAD = 'rsdn',
47 CANCEL_DOWNLOAD = 'cndn',
48 REMOVE_DOWNLOAD = 'rmdn',
49 COPY_URL_TO_CLIPBOARD = 'curl',
50 OPEN_CONTAINING_FOLDER = 'opfd',
51 };
52
53 const bigtime_t kMaxUpdateInterval = 100000LL;
54 const bigtime_t kSpeedReferenceInterval = 500000LL;
55 const bigtime_t kShowSpeedInterval = 8000000LL;
56 const bigtime_t kShowEstimatedFinishInterval = 4000000LL;
57
58 bigtime_t DownloadProgressView::sLastEstimatedFinishSpeedToggleTime = -1;
59 bool DownloadProgressView::sShowSpeed = true;
60 static const time_t kSecondsPerDay = 24 * 60 * 60;
61 static const time_t kSecondsPerHour = 60 * 60;
62
63
64 class IconView : public BView {
65 public:
IconView(const BEntry & entry)66 IconView(const BEntry& entry)
67 :
68 BView("Download icon", B_WILL_DRAW),
69 fIconBitmap(BRect(0, 0, 31, 31), 0, B_RGBA32),
70 fDimmedIcon(false)
71 {
72 SetDrawingMode(B_OP_OVER);
73 SetTo(entry);
74 }
75
IconView()76 IconView()
77 :
78 BView("Download icon", B_WILL_DRAW),
79 fIconBitmap(BRect(0, 0, 31, 31), 0, B_RGBA32),
80 fDimmedIcon(false)
81 {
82 SetDrawingMode(B_OP_OVER);
83 memset(fIconBitmap.Bits(), 0, fIconBitmap.BitsLength());
84 }
85
IconView(BMessage * archive)86 IconView(BMessage* archive)
87 :
88 BView("Download icon", B_WILL_DRAW),
89 fIconBitmap(archive),
90 fDimmedIcon(true)
91 {
92 SetDrawingMode(B_OP_OVER);
93 }
94
SetTo(const BEntry & entry)95 void SetTo(const BEntry& entry)
96 {
97 BNode node(&entry);
98 BNodeInfo info(&node);
99 info.GetTrackerIcon(&fIconBitmap, B_LARGE_ICON);
100 Invalidate();
101 }
102
SetIconDimmed(bool iconDimmed)103 void SetIconDimmed(bool iconDimmed)
104 {
105 if (fDimmedIcon != iconDimmed) {
106 fDimmedIcon = iconDimmed;
107 Invalidate();
108 }
109 }
110
IsIconDimmed() const111 bool IsIconDimmed() const
112 {
113 return fDimmedIcon;
114 }
115
SaveSettings(BMessage * archive)116 status_t SaveSettings(BMessage* archive)
117 {
118 return fIconBitmap.Archive(archive);
119 }
120
AttachedToWindow()121 virtual void AttachedToWindow()
122 {
123 AdoptParentColors();
124 }
125
Draw(BRect updateRect)126 virtual void Draw(BRect updateRect)
127 {
128 if (fDimmedIcon) {
129 SetDrawingMode(B_OP_ALPHA);
130 SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
131 SetHighColor(0, 0, 0, 100);
132 }
133 DrawBitmapAsync(&fIconBitmap);
134 }
135
MinSize()136 virtual BSize MinSize()
137 {
138 return BSize(fIconBitmap.Bounds().Width(),
139 fIconBitmap.Bounds().Height());
140 }
141
PreferredSize()142 virtual BSize PreferredSize()
143 {
144 return MinSize();
145 }
146
MaxSize()147 virtual BSize MaxSize()
148 {
149 return MinSize();
150 }
151
Bitmap()152 BBitmap* Bitmap()
153 {
154 return &fIconBitmap;
155 }
156
157 private:
158 BBitmap fIconBitmap;
159 bool fDimmedIcon;
160 };
161
162
163 class SmallButton : public BButton {
164 public:
SmallButton(const char * label,BMessage * message=NULL)165 SmallButton(const char* label, BMessage* message = NULL)
166 :
167 BButton(label, message)
168 {
169 BFont font;
170 GetFont(&font);
171 float size = ceilf(font.Size() * 0.8);
172 font.SetSize(max_c(8, size));
173 SetFont(&font, B_FONT_SIZE);
174 }
175 };
176
177
178 // #pragma mark - DownloadProgressView
179
180
DownloadProgressView(BWebDownload * download)181 DownloadProgressView::DownloadProgressView(BWebDownload* download)
182 :
183 BGroupView(B_HORIZONTAL, 8),
184 fDownload(download),
185 fURL(download->URL()),
186 fPath(download->Path())
187 {
188 }
189
190
DownloadProgressView(const BMessage * archive)191 DownloadProgressView::DownloadProgressView(const BMessage* archive)
192 :
193 BGroupView(B_HORIZONTAL, 8),
194 fDownload(NULL),
195 fURL(),
196 fPath()
197 {
198 const char* string;
199 if (archive->FindString("path", &string) == B_OK)
200 fPath.SetTo(string);
201 if (archive->FindString("url", &string) == B_OK)
202 fURL = string;
203 }
204
205
206 bool
Init(BMessage * archive)207 DownloadProgressView::Init(BMessage* archive)
208 {
209 fCurrentSize = 0;
210 fExpectedSize = 0;
211 fLastUpdateTime = 0;
212 fBytesPerSecond = 0.0;
213 for (size_t i = 0; i < kBytesPerSecondSlots; i++)
214 fBytesPerSecondSlot[i] = 0.0;
215 fCurrentBytesPerSecondSlot = 0;
216 fLastSpeedReferenceSize = 0;
217 fEstimatedFinishReferenceSize = 0;
218
219 fProcessStartTime = fLastSpeedReferenceTime
220 = fEstimatedFinishReferenceTime = system_time();
221
222 SetViewUIColor(B_LIST_BACKGROUND_COLOR);
223 SetFlags(Flags() | B_FULL_UPDATE_ON_RESIZE | B_WILL_DRAW);
224
225 if (archive) {
226 fStatusBar = new BStatusBar("download progress", fPath.Leaf());
227 float value;
228 if (archive->FindFloat("value", &value) == B_OK)
229 fStatusBar->SetTo(value);
230 } else
231 fStatusBar = new BStatusBar("download progress", "Download");
232 fStatusBar->SetMaxValue(100);
233 fStatusBar->SetBarHeight(12);
234
235 // fPath is only valid when constructed from archive (fDownload == NULL)
236 BEntry entry(fPath.Path());
237
238 if (archive) {
239 if (!entry.Exists())
240 fIconView = new IconView(archive);
241 else
242 fIconView = new IconView(entry);
243 } else
244 fIconView = new IconView();
245
246 if (!fDownload && (fStatusBar->CurrentValue() < 100 || !entry.Exists())) {
247 fTopButton = new SmallButton(B_TRANSLATE("Restart"),
248 new BMessage(RESTART_DOWNLOAD));
249 } else {
250 fTopButton = new SmallButton(B_TRANSLATE("Open"),
251 new BMessage(OPEN_DOWNLOAD));
252 fTopButton->SetEnabled(fDownload == NULL);
253 }
254 if (fDownload) {
255 fBottomButton = new SmallButton(B_TRANSLATE("Cancel"),
256 new BMessage(CANCEL_DOWNLOAD));
257 } else {
258 fBottomButton = new SmallButton(B_TRANSLATE("Remove"),
259 new BMessage(REMOVE_DOWNLOAD));
260 fBottomButton->SetEnabled(fDownload == NULL);
261 }
262
263 fInfoView = new BStringView("info view", "");
264 fInfoView->SetViewColor(ViewColor());
265
266 BSize topButtonSize = fTopButton->PreferredSize();
267 BSize bottomButtonSize = fBottomButton->PreferredSize();
268 if (bottomButtonSize.width < topButtonSize.width)
269 fBottomButton->SetExplicitMaxSize(topButtonSize);
270 else
271 fTopButton->SetExplicitMaxSize(bottomButtonSize);
272
273 BGroupLayout* layout = GroupLayout();
274 layout->SetInsets(8, 5, 5, 6);
275 layout->AddView(fIconView);
276 BView* verticalGroup = BGroupLayoutBuilder(B_VERTICAL, 3)
277 .Add(fStatusBar)
278 .Add(fInfoView)
279 .TopView()
280 ;
281 verticalGroup->SetViewColor(ViewColor());
282 layout->AddView(verticalGroup);
283
284 verticalGroup = BGroupLayoutBuilder(B_VERTICAL, 3)
285 .Add(fTopButton)
286 .Add(fBottomButton)
287 .TopView()
288 ;
289 verticalGroup->SetViewColor(ViewColor());
290 layout->AddView(verticalGroup);
291
292 BFont font;
293 fInfoView->GetFont(&font);
294 float fontSize = font.Size() * 0.8f;
295 font.SetSize(max_c(8.0f, fontSize));
296 fInfoView->SetFont(&font, B_FONT_SIZE);
297 fInfoView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
298
299 return true;
300 }
301
302
303 status_t
SaveSettings(BMessage * archive)304 DownloadProgressView::SaveSettings(BMessage* archive)
305 {
306 if (!archive)
307 return B_BAD_VALUE;
308 status_t ret = archive->AddString("path", fPath.Path());
309 if (ret == B_OK)
310 ret = archive->AddString("url", fURL.String());
311 if (ret == B_OK)
312 ret = archive->AddFloat("value", fStatusBar->CurrentValue());
313 if (ret == B_OK)
314 ret = fIconView->SaveSettings(archive);
315 return ret;
316 }
317
318
319 void
AttachedToWindow()320 DownloadProgressView::AttachedToWindow()
321 {
322 if (fDownload) {
323 fDownload->SetProgressListener(BMessenger(this));
324 // Will start node monitor upon receiving the B_DOWNLOAD_STARTED
325 // message.
326 } else {
327 BEntry entry(fPath.Path());
328 if (entry.Exists())
329 _StartNodeMonitor(entry);
330 }
331
332 fTopButton->SetTarget(this);
333 fBottomButton->SetTarget(this);
334 }
335
336
337 void
DetachedFromWindow()338 DownloadProgressView::DetachedFromWindow()
339 {
340 _StopNodeMonitor();
341 }
342
343
344 void
AllAttached()345 DownloadProgressView::AllAttached()
346 {
347 fStatusBar->SetLowColor(ViewColor());
348 fInfoView->SetLowColor(ViewColor());
349 fInfoView->SetHighUIColor(B_LIST_ITEM_TEXT_COLOR);
350
351 SetViewColor(B_TRANSPARENT_COLOR);
352 SetLowUIColor(B_LIST_BACKGROUND_COLOR);
353 if (LowColor().IsLight())
354 SetHighColor(tint_color(LowColor(), B_DARKEN_1_TINT));
355 else
356 SetHighColor(tint_color(LowColor(), B_LIGHTEN_1_TINT));
357 }
358
359
360 void
Draw(BRect updateRect)361 DownloadProgressView::Draw(BRect updateRect)
362 {
363 BRect bounds(Bounds());
364 bounds.bottom--;
365 FillRect(bounds, B_SOLID_LOW);
366 bounds.bottom++;
367 StrokeLine(bounds.LeftBottom(), bounds.RightBottom());
368 }
369
370
371 void
MessageReceived(BMessage * message)372 DownloadProgressView::MessageReceived(BMessage* message)
373 {
374 switch (message->what) {
375 case B_DOWNLOAD_STARTED:
376 {
377 BString path;
378 if (message->FindString("path", &path) != B_OK)
379 break;
380 fPath.SetTo(path);
381 BEntry entry(fPath.Path());
382 fIconView->SetTo(entry);
383 fStatusBar->Reset(fPath.Leaf());
384 _StartNodeMonitor(entry);
385
386 // Immediately switch to speed display whenever a new download
387 // starts.
388 sShowSpeed = true;
389 sLastEstimatedFinishSpeedToggleTime
390 = fProcessStartTime = fLastSpeedReferenceTime
391 = fEstimatedFinishReferenceTime = system_time();
392 break;
393 }
394 case B_DOWNLOAD_PROGRESS:
395 {
396 int64 currentSize;
397 int64 expectedSize;
398 if (message->FindInt64("current size", ¤tSize) == B_OK
399 && message->FindInt64("expected size", &expectedSize) == B_OK) {
400 _UpdateStatus(currentSize, expectedSize);
401 }
402 break;
403 }
404 case B_DOWNLOAD_REMOVED:
405 // TODO: This is a bit asymetric. The removed notification
406 // arrives here, but it would be nicer if it arrived
407 // at the window...
408 Window()->PostMessage(message);
409 break;
410 case OPEN_DOWNLOAD:
411 {
412 // TODO: In case of executable files, ask the user first!
413 entry_ref ref;
414 status_t status = get_ref_for_path(fPath.Path(), &ref);
415 if (status == B_OK)
416 status = be_roster->Launch(&ref);
417 if (status != B_OK && status != B_ALREADY_RUNNING) {
418 BAlert* alert = new BAlert(B_TRANSLATE("Open download error"),
419 B_TRANSLATE("The download could not be opened."),
420 B_TRANSLATE("OK"));
421 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
422 alert->Go(NULL);
423 }
424 break;
425 }
426 case RESTART_DOWNLOAD:
427 {
428 // We can't create a download without a full web context (mainly
429 // because it needs to access the cookie jar), and when we get here
430 // the original context is long gone (possibly the browser was
431 // restarted). So we create a new window to restart the download
432 // in a fresh context.
433 // FIXME this has of course the huge downside of leaving the new
434 // window open with a blank page. I can't think of a better
435 // solution right now...
436 BMessage* request = new BMessage(NEW_WINDOW);
437 request->AddString("url", fURL);
438 be_app->PostMessage(request);
439 break;
440 }
441
442 case CANCEL_DOWNLOAD:
443 CancelDownload();
444 break;
445
446 case REMOVE_DOWNLOAD:
447 {
448 Window()->PostMessage(SAVE_SETTINGS);
449 RemoveSelf();
450 delete this;
451 // TOAST!
452 return;
453 }
454 case B_NODE_MONITOR:
455 {
456 int32 opCode;
457 if (message->FindInt32("opcode", &opCode) != B_OK)
458 break;
459 switch (opCode) {
460 case B_ENTRY_REMOVED:
461 fIconView->SetIconDimmed(true);
462 CancelDownload();
463 break;
464 case B_ENTRY_MOVED:
465 {
466 // Follow the entry to the new location
467 dev_t device;
468 ino_t directory;
469 const char* name;
470 if (message->FindInt32("device",
471 reinterpret_cast<int32*>(&device)) != B_OK
472 || message->FindInt64("to directory",
473 reinterpret_cast<int64*>(&directory)) != B_OK
474 || message->FindString("name", &name) != B_OK
475 || strlen(name) == 0) {
476 break;
477 }
478 // Construct the BEntry and update fPath
479 entry_ref ref(device, directory, name);
480 BEntry entry(&ref);
481 if (entry.GetPath(&fPath) != B_OK)
482 break;
483
484 // Find out if the directory is the Trash for this
485 // volume
486 char trashPath[B_PATH_NAME_LENGTH];
487 if (find_directory(B_TRASH_DIRECTORY, device, false,
488 trashPath, B_PATH_NAME_LENGTH) == B_OK) {
489 BPath trashDirectory(trashPath);
490 BPath parentDirectory;
491 fPath.GetParent(&parentDirectory);
492 if (parentDirectory == trashDirectory) {
493 // The entry was moved into the Trash.
494 // If the download is still in progress,
495 // cancel it.
496 fIconView->SetIconDimmed(true);
497 CancelDownload();
498 break;
499 } else if (fIconView->IsIconDimmed()) {
500 // Maybe it was moved out of the trash.
501 fIconView->SetIconDimmed(false);
502 }
503 }
504
505 // Inform download of the new path
506 if (fDownload)
507 fDownload->HasMovedTo(fPath);
508
509 float value = fStatusBar->CurrentValue();
510 fStatusBar->Reset(name);
511 fStatusBar->SetTo(value);
512 Window()->PostMessage(SAVE_SETTINGS);
513 break;
514 }
515 case B_ATTR_CHANGED:
516 {
517 BEntry entry(fPath.Path());
518 fIconView->SetIconDimmed(false);
519 fIconView->SetTo(entry);
520 break;
521 }
522 }
523 break;
524 }
525
526 // Context menu messages
527 case COPY_URL_TO_CLIPBOARD:
528 if (be_clipboard->Lock()) {
529 BMessage* data = be_clipboard->Data();
530 if (data != NULL) {
531 be_clipboard->Clear();
532 data->AddData("text/plain", B_MIME_TYPE, fURL.String(),
533 fURL.Length());
534 }
535 be_clipboard->Commit();
536 be_clipboard->Unlock();
537 }
538 break;
539 case OPEN_CONTAINING_FOLDER:
540 if (fPath.InitCheck() == B_OK) {
541 BEntry selected(fPath.Path());
542 if (!selected.Exists())
543 break;
544
545 BPath containingFolder;
546 if (fPath.GetParent(&containingFolder) != B_OK)
547 break;
548 entry_ref ref;
549 if (get_ref_for_path(containingFolder.Path(), &ref) != B_OK)
550 break;
551
552 // Ask Tracker to open the containing folder and select the
553 // file inside it.
554 BMessenger trackerMessenger("application/x-vnd.Be-TRAK");
555
556 if (trackerMessenger.IsValid()) {
557 BMessage selectionCommand(B_REFS_RECEIVED);
558 selectionCommand.AddRef("refs", &ref);
559
560 node_ref selectedRef;
561 if (selected.GetNodeRef(&selectedRef) == B_OK) {
562 selectionCommand.AddData("nodeRefToSelect", B_RAW_TYPE,
563 (void*)&selectedRef, sizeof(node_ref));
564 }
565
566 trackerMessenger.SendMessage(&selectionCommand);
567 }
568 }
569 break;
570
571 default:
572 BGroupView::MessageReceived(message);
573 }
574 }
575
576
577 void
ShowContextMenu(BPoint screenWhere)578 DownloadProgressView::ShowContextMenu(BPoint screenWhere)
579 {
580 screenWhere += BPoint(2, 2);
581
582 BPopUpMenu* contextMenu = new BPopUpMenu("download context");
583 BMenuItem* copyURL = new BMenuItem(B_TRANSLATE("Copy URL to clipboard"),
584 new BMessage(COPY_URL_TO_CLIPBOARD));
585 copyURL->SetEnabled(fURL.Length() > 0);
586 contextMenu->AddItem(copyURL);
587 BMenuItem* openFolder = new BMenuItem(B_TRANSLATE("Open containing folder"),
588 new BMessage(OPEN_CONTAINING_FOLDER));
589 contextMenu->AddItem(openFolder);
590
591 contextMenu->SetTargetForItems(this);
592 contextMenu->Go(screenWhere, true, true, true);
593 }
594
595
596 BWebDownload*
Download() const597 DownloadProgressView::Download() const
598 {
599 return fDownload;
600 }
601
602
603 const BString&
URL() const604 DownloadProgressView::URL() const
605 {
606 return fURL;
607 }
608
609
610 bool
IsMissing() const611 DownloadProgressView::IsMissing() const
612 {
613 return fIconView->IsIconDimmed();
614 }
615
616
617 bool
IsFinished() const618 DownloadProgressView::IsFinished() const
619 {
620 return !fDownload && fStatusBar->CurrentValue() == 100;
621 }
622
623
624 void
DownloadFinished()625 DownloadProgressView::DownloadFinished()
626 {
627 fDownload = NULL;
628 if (fExpectedSize == -1) {
629 fStatusBar->SetTo(100.0);
630 fExpectedSize = fCurrentSize;
631 }
632 fTopButton->SetEnabled(true);
633 fBottomButton->SetLabel(B_TRANSLATE("Remove"));
634 fBottomButton->SetMessage(new BMessage(REMOVE_DOWNLOAD));
635 fBottomButton->SetEnabled(true);
636 fInfoView->SetText("");
637 fStatusBar->SetBarColor(ui_color(B_SUCCESS_COLOR));
638
639 BNotification success(B_INFORMATION_NOTIFICATION);
640 success.SetGroup(B_TRANSLATE("WebPositive"));
641 success.SetTitle(B_TRANSLATE("Download finished"));
642 success.SetContent(fPath.Leaf());
643 BEntry entry(fPath.Path());
644 entry_ref ref;
645 entry.GetRef(&ref);
646 success.SetOnClickFile(&ref);
647 success.SetIcon(fIconView->Bitmap());
648 success.Send();
649
650 }
651
652
653 void
CancelDownload()654 DownloadProgressView::CancelDownload()
655 {
656 // Show the cancel notification, and set the progress bar red, only if the
657 // download was still running. In cases where the file is deleted after
658 // the download was finished, we don't want these things to happen.
659 if (fDownload) {
660 // Also cancel the download
661 fDownload->Cancel();
662 BNotification success(B_ERROR_NOTIFICATION);
663 success.SetGroup(B_TRANSLATE("WebPositive"));
664 success.SetTitle(B_TRANSLATE("Download aborted"));
665 success.SetContent(fPath.Leaf());
666 // Don't make a click on the notification open the file: it is not
667 // complete
668 success.SetIcon(fIconView->Bitmap());
669 success.Send();
670
671 fStatusBar->SetBarColor(ui_color(B_FAILURE_COLOR));
672 }
673
674 fDownload = NULL;
675 fTopButton->SetLabel(B_TRANSLATE("Restart"));
676 fTopButton->SetMessage(new BMessage(RESTART_DOWNLOAD));
677 fTopButton->SetEnabled(true);
678 fBottomButton->SetLabel(B_TRANSLATE("Remove"));
679 fBottomButton->SetMessage(new BMessage(REMOVE_DOWNLOAD));
680 fBottomButton->SetEnabled(true);
681 fInfoView->SetText("");
682
683 fPath.Unset();
684 }
685
686
687 /*static*/ void
SpeedVersusEstimatedFinishTogglePulse()688 DownloadProgressView::SpeedVersusEstimatedFinishTogglePulse()
689 {
690 bigtime_t now = system_time();
691 if (sShowSpeed
692 && sLastEstimatedFinishSpeedToggleTime + kShowSpeedInterval
693 <= now) {
694 sShowSpeed = false;
695 sLastEstimatedFinishSpeedToggleTime = now;
696 } else if (!sShowSpeed
697 && sLastEstimatedFinishSpeedToggleTime
698 + kShowEstimatedFinishInterval <= now) {
699 sShowSpeed = true;
700 sLastEstimatedFinishSpeedToggleTime = now;
701 }
702 }
703
704
705 // #pragma mark - private
706
707
708 void
_UpdateStatus(off_t currentSize,off_t expectedSize)709 DownloadProgressView::_UpdateStatus(off_t currentSize, off_t expectedSize)
710 {
711 fCurrentSize = currentSize;
712 fExpectedSize = expectedSize;
713
714 fStatusBar->SetTo(100.0 * currentSize / expectedSize);
715
716 bigtime_t currentTime = system_time();
717 if ((currentTime - fLastUpdateTime) > kMaxUpdateInterval) {
718 fLastUpdateTime = currentTime;
719
720 if (currentTime >= fLastSpeedReferenceTime + kSpeedReferenceInterval) {
721 // update current speed every kSpeedReferenceInterval
722 fCurrentBytesPerSecondSlot
723 = (fCurrentBytesPerSecondSlot + 1) % kBytesPerSecondSlots;
724 fBytesPerSecondSlot[fCurrentBytesPerSecondSlot]
725 = (double)(currentSize - fLastSpeedReferenceSize)
726 * 1000000LL / (currentTime - fLastSpeedReferenceTime);
727 fLastSpeedReferenceSize = currentSize;
728 fLastSpeedReferenceTime = currentTime;
729 fBytesPerSecond = 0.0;
730 size_t count = 0;
731 for (size_t i = 0; i < kBytesPerSecondSlots; i++) {
732 if (fBytesPerSecondSlot[i] != 0.0) {
733 fBytesPerSecond += fBytesPerSecondSlot[i];
734 count++;
735 }
736 }
737 if (count > 0)
738 fBytesPerSecond /= count;
739 }
740 _UpdateStatusText();
741 }
742 }
743
744
745 void
_UpdateStatusText()746 DownloadProgressView::_UpdateStatusText()
747 {
748 fInfoView->SetText("");
749 BString buffer;
750 if (sShowSpeed && fBytesPerSecond != 0.0) {
751 // Draw speed info
752 char sizeBuffer[128];
753 // Get strings for current and expected size and remove the unit
754 // from the current size string if it's the same as the expected
755 // size unit.
756 BString currentSize = string_for_size((double)fCurrentSize, sizeBuffer,
757 sizeof(sizeBuffer));
758 BString expectedSize = string_for_size((double)fExpectedSize, sizeBuffer,
759 sizeof(sizeBuffer));
760 int currentSizeUnitPos = currentSize.FindLast(' ');
761 int expectedSizeUnitPos = expectedSize.FindLast(' ');
762 if (currentSizeUnitPos >= 0 && expectedSizeUnitPos >= 0
763 && strcmp(currentSize.String() + currentSizeUnitPos,
764 expectedSize.String() + expectedSizeUnitPos) == 0) {
765 currentSize.Truncate(currentSizeUnitPos);
766 }
767
768 buffer = B_TRANSLATE("(%currentSize% of %expectedSize%, %rate%/s)");
769 buffer.ReplaceFirst("%currentSize%", currentSize);
770 buffer.ReplaceFirst("%expectedSize%", expectedSize);
771 buffer.ReplaceFirst("%rate%", string_for_size(fBytesPerSecond,
772 sizeBuffer, sizeof(sizeBuffer)));
773
774 float stringWidth = fInfoView->StringWidth(buffer.String());
775 if (stringWidth < fInfoView->Bounds().Width())
776 fInfoView->SetText(buffer.String());
777 else {
778 // complete string too wide, try with shorter version
779 buffer = string_for_size(fBytesPerSecond, sizeBuffer,
780 sizeof(sizeBuffer));
781 buffer << B_TRANSLATE_COMMENT("/s)", "...as in 'per second'");
782 stringWidth = fInfoView->StringWidth(buffer.String());
783 if (stringWidth < fInfoView->Bounds().Width())
784 fInfoView->SetText(buffer.String());
785 }
786 } else if (!sShowSpeed && fCurrentSize < fExpectedSize) {
787 double totalBytesPerSecond = (double)(fCurrentSize
788 - fEstimatedFinishReferenceSize)
789 * 1000000LL / (system_time() - fEstimatedFinishReferenceTime);
790 double secondsRemaining = (fExpectedSize - fCurrentSize)
791 / totalBytesPerSecond;
792 time_t now = (time_t)real_time_clock();
793 time_t finishTime = (time_t)(now + secondsRemaining);
794
795 BString timeText;
796 if (finishTime - now > kSecondsPerDay) {
797 BDateTimeFormat().Format(timeText, finishTime,
798 B_MEDIUM_DATE_FORMAT, B_MEDIUM_TIME_FORMAT);
799 } else {
800 BTimeFormat().Format(timeText, finishTime,
801 B_MEDIUM_TIME_FORMAT);
802 }
803
804 BString statusString;
805 BDurationFormat formatter;
806 BString finishString;
807 if (finishTime - now > kSecondsPerHour) {
808 statusString.SetTo(B_TRANSLATE("(Finish: %date - Over %duration left)"));
809 formatter.Format(finishString, now * 1000000LL, finishTime * 1000000LL);
810 } else {
811 statusString.SetTo(B_TRANSLATE("(Finish: %date - %duration left)"));
812 formatter.Format(finishString, now * 1000000LL, finishTime * 1000000LL);
813 }
814
815 statusString.ReplaceFirst("%date", timeText);
816 statusString.ReplaceFirst("%duration", finishString);
817
818 float stringWidth = fInfoView->StringWidth(statusString.String());
819 if (stringWidth < fInfoView->Bounds().Width())
820 fInfoView->SetText(statusString.String());
821 else {
822 // complete string too wide, try with shorter version
823 statusString.SetTo(B_TRANSLATE("(Finish: %date)"));
824 statusString.ReplaceFirst("%date", timeText);
825 stringWidth = fInfoView->StringWidth(statusString.String());
826 if (stringWidth < fInfoView->Bounds().Width())
827 fInfoView->SetText(statusString.String());
828 }
829 }
830 }
831
832
833 void
_StartNodeMonitor(const BEntry & entry)834 DownloadProgressView::_StartNodeMonitor(const BEntry& entry)
835 {
836 node_ref nref;
837 if (entry.GetNodeRef(&nref) == B_OK)
838 watch_node(&nref, B_WATCH_ALL, this);
839 }
840
841
842 void
_StopNodeMonitor()843 DownloadProgressView::_StopNodeMonitor()
844 {
845 stop_watching(this);
846 }
847
848