/* * Copyright (c) 2008-2023 Jonathan Schleifer <js@nil.im> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #include <math.h> #import "OFDate.h" #import "OFStdIOStream.h" #import "OFTimer.h" #import "OFLocale.h" #import "ProgressBar.h" static const float oneKibibyte = 1024; static const float oneMebibyte = 1024 * 1024; static const float oneGibibyte = 1024 * 1024 * 1024; static const OFTimeInterval updateInterval = 0.1; #ifdef OF_MINT /* freemint-gcc does not have trunc() */ # define trunc(x) ((int64_t)(x)) #endif #ifndef HAVE_TRUNCF # define truncf(x) trunc(x) #endif @implementation ProgressBar - (instancetype)initWithLength: (unsigned long long)length resumedFrom: (unsigned long long)resumedFrom useUnicode: (bool)useUnicode { self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); _useUnicode = useUnicode; _length = length; _resumedFrom = resumedFrom; _startDate = [[OFDate alloc] init]; _lastReceivedDate = [[OFDate alloc] init]; _drawTimer = [[OFTimer scheduledTimerWithTimeInterval: updateInterval target: self selector: @selector(draw) repeats: true] retain]; _BPSTimer = [[OFTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: @selector( calculateBPSAndETA) repeats: true] retain]; objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [self stop]; [_startDate release]; [_lastReceivedDate release]; [_drawTimer release]; [_BPSTimer release]; [super dealloc]; } - (void)setReceived: (unsigned long long)received { _received = received; } - (void)_drawProgress { float bars, percent; int columns, barWidth; if ((columns = OFStdOut.columns) >= 0) { if (columns > 37) barWidth = columns - 37; else barWidth = 0; } else barWidth = 43; bars = (float)(_resumedFrom + _received) / (float)(_resumedFrom + _length) * barWidth; percent = (float)(_resumedFrom + _received) / (float)(_resumedFrom + _length) * 100; if (_useUnicode) { [OFStdOut writeString: @"\r ▕"]; for (size_t i = 0; i < (size_t)bars; i++) [OFStdOut writeString: @"█"]; if (bars < barWidth) { float rem = bars - truncf(bars); if (rem >= 0.875) [OFStdOut writeString: @"▉"]; else if (rem >= 0.75) [OFStdOut writeString: @"▊"]; else if (rem >= 0.625) [OFStdOut writeString: @"▋"]; else if (rem >= 0.5) [OFStdOut writeString: @"▌"]; else if (rem >= 0.375) [OFStdOut writeString: @"▍"]; else if (rem >= 0.25) [OFStdOut writeString: @"▎"]; else if (rem >= 0.125) [OFStdOut writeString: @"▏"]; else [OFStdOut writeString: @" "]; for (size_t i = 0; i < barWidth - (size_t)bars - 1; i++) [OFStdOut writeString: @" "]; } [OFStdOut writeFormat: @"▏ %,6.2f%% ", percent]; } else { [OFStdOut writeString: @"\r ["]; for (size_t i = 0; i < (size_t)bars; i++) [OFStdOut writeString: @"#"]; if (bars < barWidth) { float rem = bars - truncf(bars); if (rem >= 0.75) [OFStdOut writeString: @"O"]; else if (rem >= 0.5) [OFStdOut writeString: @"o"]; else if (rem >= 0.25) [OFStdOut writeString: @"."]; else [OFStdOut writeString: @" "]; for (size_t i = 0; i < barWidth - (size_t)bars - 1; i++) [OFStdOut writeString: @" "]; } [OFStdOut writeFormat: @"] %,6.2f%% ", percent]; } if (percent == 100) { double timeInterval = -_startDate.timeIntervalSinceNow; _BPS = (float)_received / (float)timeInterval; _ETA = timeInterval; } if (isinf(_ETA)) [OFStdOut writeString: @"--:--:-- "]; else if (_ETA >= 99 * 3600) { OFString *num = [OFString stringWithFormat: @"%,4.2f", _ETA / (24 * 3600)]; [OFStdOut writeString: OF_LOCALIZED(@"eta_days", @"%[num] d ", @"num", num)]; } else [OFStdOut writeFormat: @"%2u:%02u:%02u ", (uint8_t)(_ETA / 3600), (uint8_t)(_ETA / 60) % 60, (uint8_t)_ETA % 60]; if (_BPS >= oneGibibyte) { OFString *num = [OFString stringWithFormat: @"%,7.2f", _BPS / oneGibibyte]; [OFStdOut writeString: OF_LOCALIZED(@"progress_gibs", @"%[num] GiB/s", @"num", num)]; } else if (_BPS >= oneMebibyte) { OFString *num = [OFString stringWithFormat: @"%,7.2f", _BPS / oneMebibyte]; [OFStdOut writeString: OF_LOCALIZED(@"progress_mibs", @"%[num] MiB/s", @"num", num)]; } else if (_BPS >= oneKibibyte) { OFString *num = [OFString stringWithFormat: @"%,7.2f", _BPS / oneKibibyte]; [OFStdOut writeString: OF_LOCALIZED(@"progress_kibs", @"%[num] KiB/s", @"num", num)]; } else { OFString *num = [OFString stringWithFormat: @"%,7.2f", _BPS]; [OFStdOut writeString: OF_LOCALIZED(@"progress_bps", @"%[num] B/s ", @"num", num)]; } } - (void)_drawReceived { [OFStdOut writeString: @"\r "]; if (_resumedFrom + _received >= oneGibibyte) { OFString *num = [OFString stringWithFormat: @"%,7.2f", (float)(_resumedFrom + _received) / oneGibibyte]; [OFStdOut writeString: OF_LOCALIZED(@"progress_gib", @"%[num] GiB", @"num", num)]; } else if (_resumedFrom + _received >= oneMebibyte) { OFString *num = [OFString stringWithFormat: @"%,7.2f", (float)(_resumedFrom + _received) / oneMebibyte]; [OFStdOut writeString: OF_LOCALIZED(@"progress_mib", @"%[num] MiB", @"num", num)]; } else if (_resumedFrom + _received >= oneKibibyte) { OFString *num = [OFString stringWithFormat: @"%,7.2f", (float)(_resumedFrom + _received) / oneKibibyte]; [OFStdOut writeString: OF_LOCALIZED(@"progress_kib", @"%[num] KiB", @"num", num)]; } else { OFString *num = [OFString stringWithFormat: @"%jd", _resumedFrom + _received]; [OFStdOut writeString: OF_LOCALIZED(@"progress_bytes", @"[" @" [" @" {'num == 1': '1 byte '}," @" {'': '%[num] bytes'}" @" ]" @"]".objectByParsingJSON, @"num", num)]; } [OFStdOut writeString: @" "]; if (_stopped) _BPS = (float)_received / -(float)_startDate.timeIntervalSinceNow; if (_BPS >= oneGibibyte) { OFString *num = [OFString stringWithFormat: @"%,7.2f", _BPS / oneGibibyte]; [OFStdOut writeString: OF_LOCALIZED(@"progress_gibs", @"%[num] GiB/s", @"num", num)]; } else if (_BPS >= oneMebibyte) { OFString *num = [OFString stringWithFormat: @"%,7.2f", _BPS / oneMebibyte]; [OFStdOut writeString: OF_LOCALIZED(@"progress_mibs", @"%[num] MiB/s", @"num", num)]; } else if (_BPS >= oneKibibyte) { OFString *num = [OFString stringWithFormat: @"%,7.2f", _BPS / oneKibibyte]; [OFStdOut writeString: OF_LOCALIZED(@"progress_kibs", @"%[num] KiB/s", @"num", num)]; } else { OFString *num = [OFString stringWithFormat: @"%,7.2f", _BPS]; [OFStdOut writeString: OF_LOCALIZED(@"progress_bps", @"%[num] B/s ", @"num", num)]; } } - (void)draw { if (_length > 0) [self _drawProgress]; else [self _drawReceived]; } - (void)calculateBPSAndETA { _BPSWindow[_BPSWindowIndex++ % BPS_WINDOW_SIZE] = (float)(_received - _lastReceived) / -(float)_lastReceivedDate.timeIntervalSinceNow; if (_BPSWindowLength < BPS_WINDOW_SIZE) _BPSWindowLength++; _BPS = 0; for (size_t i = 0; i < _BPSWindowLength; i++) _BPS += _BPSWindow[i]; _BPS /= _BPSWindowLength; _ETA = (double)(_length - _received) / _BPS; _lastReceived = _received; [_lastReceivedDate release]; _lastReceivedDate = [[OFDate alloc] init]; } - (void)stop { [_drawTimer invalidate]; [_BPSTimer invalidate]; _stopped = true; } @end