mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-26 05:18:31 -04:00
Download Progress
This commit is contained in:
parent
971dc53ab9
commit
4ff054f0e5
@ -105,7 +105,14 @@ class DownloadTasksController: UITableViewController, NSFetchedResultsController
|
||||
guard let progress = Network.shared.operations[id]?.progress else {return}
|
||||
cell.progressLabel.text = progress.fractionCompletedDescription
|
||||
cell.progressView.setProgress(Float(progress.fractionCompleted), animated: animated)
|
||||
cell.detailLabel.text = progress.localizedAdditionalDescription.stringByReplacingOccurrencesOfString(" – ", withString: "\n")
|
||||
cell.detailLabel.text = {
|
||||
let string = progress.progressAndSpeedDescription
|
||||
if string.containsString(" — ") {
|
||||
return string.stringByReplacingOccurrencesOfString(" — ", withString: "\n")
|
||||
} else {
|
||||
return string + "\n" + NSLocalizedString("Estimating Speed and Remaining Time", comment: "")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// MARK: Other Data Source
|
||||
@ -147,13 +154,13 @@ class DownloadTasksController: UITableViewController, NSFetchedResultsController
|
||||
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {}
|
||||
|
||||
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
|
||||
let remove = UITableViewRowAction(style: .Destructive, title: LocalizedStrings.remove) { (action, indexPath) -> Void in
|
||||
let cancel = UITableViewRowAction(style: .Destructive, title: LocalizedStrings.Common.cancel) { (action, indexPath) -> Void in
|
||||
guard let downloadTask = self.fetchedResultController.objectAtIndexPath(indexPath) as? DownloadTask,
|
||||
let bookID = downloadTask.book?.id else {return}
|
||||
let operation = CancelBookDownloadOperation(bookID: bookID)
|
||||
GlobalOperationQueue.sharedInstance.addOperation(operation)
|
||||
}
|
||||
return [remove]
|
||||
return [cancel]
|
||||
}
|
||||
|
||||
// MARK: - Fetched Results Controller
|
||||
|
@ -49,7 +49,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.7.1441</string>
|
||||
<string>1.7.1481</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
@ -1,129 +0,0 @@
|
||||
//
|
||||
// DownloadProgress.swift
|
||||
// Kiwix
|
||||
//
|
||||
// Created by Chris Li on 3/23/16.
|
||||
// Copyright © 2016 Chris. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class DownloadProgress: NSProgress {
|
||||
let book: Book
|
||||
private let observationCount = 9
|
||||
private let sampleFrequency: NSTimeInterval = 0.3
|
||||
private var speeds = [Double]()
|
||||
private var timer: NSTimer?
|
||||
private weak var task: NSURLSessionDownloadTask?
|
||||
|
||||
init(book: Book) {
|
||||
self.book = book
|
||||
super.init(parent: nil, userInfo: [NSProgressFileOperationKindKey: NSProgressFileOperationKindDownloading])
|
||||
self.kind = NSProgressKindFile
|
||||
self.totalUnitCount = book.fileSize
|
||||
self.completedUnitCount = book.downloadTask?.totalBytesWritten ?? 0
|
||||
print("totalBytesWritten: \(book.downloadTask?.totalBytesWritten)")
|
||||
}
|
||||
|
||||
deinit {
|
||||
timer?.invalidate()
|
||||
}
|
||||
|
||||
func downloadStarted(task: NSURLSessionDownloadTask) {
|
||||
self.task = task
|
||||
recordSpeed()
|
||||
timer = NSTimer.scheduledTimerWithTimeInterval(sampleFrequency, target: self, selector: #selector(DownloadProgress.recordSpeed), userInfo: nil, repeats: true)
|
||||
}
|
||||
|
||||
func downloadTerminated() {
|
||||
timer?.invalidate()
|
||||
speeds.removeAll()
|
||||
setUserInfoObject(nil, forKey: NSProgressThroughputKey)
|
||||
setUserInfoObject(nil, forKey: NSProgressEstimatedTimeRemainingKey)
|
||||
}
|
||||
|
||||
func recordSpeed() {
|
||||
guard let task = task else {return}
|
||||
|
||||
/*
|
||||
Check if the countOfBytesReceived and countOfBytesExpectedToReceive in NSURLSessionDownloadTask
|
||||
object is both zero. When a NSURLSessionDownloadTask resumes, these two value will be zero at first.
|
||||
We don't want to accidently set these two values in this progress object to be all 0.
|
||||
*/
|
||||
guard task.countOfBytesReceived != 0 && task.countOfBytesExpectedToReceive != 0 else {return}
|
||||
|
||||
let previousCompletedUnitCount = completedUnitCount
|
||||
completedUnitCount = task.countOfBytesReceived
|
||||
totalUnitCount = task.countOfBytesExpectedToReceive
|
||||
let speed = Double(completedUnitCount - previousCompletedUnitCount) / sampleFrequency
|
||||
speeds.insert(speed, atIndex: 0)
|
||||
if speeds.count > observationCount {speeds.popLast()}
|
||||
}
|
||||
|
||||
var maSpeed: Double? {
|
||||
let alpha = 0.5
|
||||
var remainingWeight = 1.0
|
||||
var speedMA = 0.0
|
||||
|
||||
guard speeds.count >= observationCount else {return nil}
|
||||
for index in 0..<speeds.count {
|
||||
let weight = alpha * pow(1.0 - alpha, Double(index))
|
||||
remainingWeight -= weight
|
||||
speedMA += weight * speeds[index]
|
||||
}
|
||||
speedMA += remainingWeight * (speeds.last ?? 0.0)
|
||||
return speedMA > 0.0 ? speedMA : nil
|
||||
}
|
||||
|
||||
// MARK: - Descriptions
|
||||
|
||||
var sizeDescription: String {
|
||||
return localizedAdditionalDescription.componentsSeparatedByString(" — ").first ?? ""
|
||||
}
|
||||
|
||||
var speedAndRemainingTimeDescription: String? {
|
||||
guard let maSpeed = self.maSpeed else {return nil}
|
||||
setUserInfoObject(NSNumber(double: maSpeed), forKey: NSProgressThroughputKey)
|
||||
|
||||
let remainingSeconds = Double(totalUnitCount - completedUnitCount) / maSpeed
|
||||
setUserInfoObject(NSNumber(double: remainingSeconds), forKey: NSProgressEstimatedTimeRemainingKey)
|
||||
|
||||
let components = localizedAdditionalDescription.componentsSeparatedByString(" — ")
|
||||
return components.count > 1 ? components.last : nil
|
||||
}
|
||||
|
||||
var percentDescription: String {
|
||||
let formatter = NSNumberFormatter()
|
||||
formatter.numberStyle = .PercentStyle
|
||||
formatter.maximumIntegerDigits = 3
|
||||
formatter.maximumFractionDigits = 0
|
||||
formatter.locale = NSLocale.currentLocale()
|
||||
return formatter.stringFromNumber(NSNumber(double: fractionCompleted)) ?? ""
|
||||
}
|
||||
|
||||
var sizeAndPercentDescription: String {
|
||||
var strings = [String]()
|
||||
strings.append(sizeDescription)
|
||||
strings.append(percentDescription)
|
||||
return strings.joinWithSeparator(" - ")
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
guard let state = book.downloadTask?.state else {return " \n "}
|
||||
switch state {
|
||||
case .Queued: return sizeAndPercentDescription + "\n" + LocalizedStrings.queued
|
||||
case .Downloading: return sizeAndPercentDescription + "\n" + (speedAndRemainingTimeDescription ?? LocalizedStrings.estimatingSpeedAndRemainingTime)
|
||||
case .Paused: return sizeAndPercentDescription + "\n" + LocalizedStrings.paused
|
||||
case .Error: return sizeDescription + "\n" + LocalizedStrings.downloadError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedStrings {
|
||||
class var starting: String {return NSLocalizedString("Starting", comment: "Library: Download task description")}
|
||||
class var resuming: String {return NSLocalizedString("Resuming", comment: "Library: Download task description")}
|
||||
class var paused: String {return NSLocalizedString("Paused", comment: "Library: Download task description")}
|
||||
class var downloadError: String {return NSLocalizedString("Download Error", comment: "Library: Download task description")}
|
||||
class var queued: String {return NSLocalizedString("Queued", comment: "Library: Download task description")}
|
||||
class var estimatingSpeedAndRemainingTime: String {return NSLocalizedString("Estimating speed and remaining time", comment: "Library: Download task description")}
|
||||
}
|
@ -469,11 +469,11 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" rowHeight="92" id="ekT-ed-PU9" customClass="DownloadBookCell" customModule="Kiwix" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="92" width="375" height="92"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" rowHeight="82" id="ekT-ed-PU9" customClass="DownloadBookCell" customModule="Kiwix" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="92" width="375" height="82"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ekT-ed-PU9" id="oM4-Hy-Mkf">
|
||||
<frame key="frameInset" width="375" height="91"/>
|
||||
<frame key="frameInset" width="375" height="81.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" lineBreakMode="tailTruncation" minimumFontSize="8" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Hji-3G-yaJ">
|
||||
@ -506,7 +506,7 @@
|
||||
<constraint firstItem="6Zg-Xf-xgS" firstAttribute="leading" secondItem="Hji-3G-yaJ" secondAttribute="trailing" constant="2" id="5lO-Sp-t2G"/>
|
||||
<constraint firstItem="v8H-ZV-HNV" firstAttribute="leading" secondItem="oM4-Hy-Mkf" secondAttribute="leadingMargin" id="7FW-0t-ljT"/>
|
||||
<constraint firstAttribute="leadingMargin" secondItem="Too-68-SzG" secondAttribute="leading" constant="-2" id="9Vd-3e-m5f"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="v8H-ZV-HNV" secondAttribute="bottom" id="9ac-Vl-xk9"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="v8H-ZV-HNV" secondAttribute="bottom" constant="-8" id="9ac-Vl-xk9"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="6Zg-Xf-xgS" secondAttribute="trailing" id="D9Q-Dz-SXA"/>
|
||||
<constraint firstItem="g0o-rT-qxm" firstAttribute="top" secondItem="Too-68-SzG" secondAttribute="bottom" constant="4" id="IJR-yJ-4xs"/>
|
||||
<constraint firstItem="g0o-rT-qxm" firstAttribute="trailing" secondItem="oM4-Hy-Mkf" secondAttribute="trailingMargin" id="OFh-b3-2bf"/>
|
||||
|
@ -21,7 +21,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.7.1771</string>
|
||||
<string>1.7.1833</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
|
@ -238,7 +238,6 @@
|
||||
971A10471D022CBE007FC62C /* SearchResultTBVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SearchResultTBVC.swift; path = "Kiwix-iOS/Controller/SearchResultTBVC.swift"; sourceTree = SOURCE_ROOT; };
|
||||
971A10481D022CBE007FC62C /* SearchBooksVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SearchBooksVC.swift; path = "Kiwix-iOS/Controller/SearchBooksVC.swift"; sourceTree = SOURCE_ROOT; };
|
||||
971A10511D022D9D007FC62C /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
971A106D1D022E62007FC62C /* DownloadProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DownloadProgress.swift; path = "Kiwix-iOS/Model/DownloadProgress.swift"; sourceTree = SOURCE_ROOT; };
|
||||
971A106E1D022E62007FC62C /* Network_old.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Network_old.swift; path = "Kiwix-iOS/Model/Network_old.swift"; sourceTree = SOURCE_ROOT; };
|
||||
971A10781D022F05007FC62C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
971A107A1D022F74007FC62C /* DownloaderLearnMore.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = DownloaderLearnMore.html; path = Kiwix/HelpDocuments/DownloaderLearnMore.html; sourceTree = SOURCE_ROOT; };
|
||||
@ -571,7 +570,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
971A106E1D022E62007FC62C /* Network_old.swift */,
|
||||
971A106D1D022E62007FC62C /* DownloadProgress.swift */,
|
||||
);
|
||||
name = Network;
|
||||
path = Kiwix;
|
||||
|
@ -48,12 +48,10 @@ class DownloadBookOperation: URLSessionDownloadTaskOperation {
|
||||
}
|
||||
|
||||
class DownloadProgress: NSProgress {
|
||||
init(completedUnitCount: Int64, totalUnitCount: Int64) {
|
||||
super.init(parent: nil, userInfo: [NSProgressFileOperationKindKey: NSProgressFileOperationKindDownloading])
|
||||
self.kind = NSProgressKindFile
|
||||
self.totalUnitCount = totalUnitCount
|
||||
self.completedUnitCount = completedUnitCount
|
||||
}
|
||||
typealias TimePoint = (completedUnitCount: Int64, timeStamp: NSTimeInterval)
|
||||
private var timePoints = [TimePoint]()
|
||||
private let timePointMinCount: Int = 20
|
||||
private let timePointMaxCount: Int = 200
|
||||
|
||||
private lazy var percentFormatter: NSNumberFormatter = {
|
||||
let formatter = NSNumberFormatter()
|
||||
@ -65,9 +63,62 @@ class DownloadProgress: NSProgress {
|
||||
return formatter
|
||||
}()
|
||||
|
||||
init(completedUnitCount: Int64 = 0, totalUnitCount: Int64) {
|
||||
super.init(parent: nil, userInfo: [NSProgressFileOperationKindKey: NSProgressFileOperationKindDownloading])
|
||||
self.kind = NSProgressKindFile
|
||||
self.totalUnitCount = totalUnitCount
|
||||
self.completedUnitCount = completedUnitCount
|
||||
}
|
||||
|
||||
override var completedUnitCount: Int64 {
|
||||
didSet {
|
||||
add(completedUnitCount)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Descriptions
|
||||
|
||||
var fractionCompletedDescription: String? {
|
||||
return percentFormatter.stringFromNumber(NSNumber(double: fractionCompleted))
|
||||
}
|
||||
|
||||
var progressAndSpeedDescription: String! {
|
||||
calculateSpeed()
|
||||
return localizedAdditionalDescription
|
||||
}
|
||||
|
||||
func calculateSpeed() {
|
||||
guard self.timePoints.count >= timePointMinCount else {return}
|
||||
|
||||
let smoothingFactor = 1 / Double(self.timePoints.count)
|
||||
var timePoints = self.timePoints
|
||||
var oldPoint = timePoints.removeFirst()
|
||||
let recentPoint = timePoints.removeFirst()
|
||||
var averageSpeed: Double = Double(recentPoint.completedUnitCount - oldPoint.completedUnitCount) / (recentPoint.timeStamp - oldPoint.timeStamp)
|
||||
oldPoint = recentPoint
|
||||
|
||||
for recentPoint in timePoints {
|
||||
let lastSpeed = Double(recentPoint.completedUnitCount - oldPoint.completedUnitCount) / (recentPoint.timeStamp - oldPoint.timeStamp)
|
||||
oldPoint = recentPoint
|
||||
averageSpeed = smoothingFactor * lastSpeed + (1 - smoothingFactor) * averageSpeed
|
||||
}
|
||||
|
||||
setUserInfoObject(NSNumber(double: averageSpeed), forKey: NSProgressThroughputKey)
|
||||
|
||||
let remainingSeconds = Double(totalUnitCount - completedUnitCount) / averageSpeed
|
||||
setUserInfoObject(NSNumber(double: remainingSeconds), forKey: NSProgressEstimatedTimeRemainingKey)
|
||||
}
|
||||
|
||||
private func add(completedUnitCount: Int64) {
|
||||
let timeStamp = NSDate().timeIntervalSince1970
|
||||
if let lastPoint = timePoints.last {
|
||||
guard timeStamp - lastPoint.timeStamp > 0.2 else {return}
|
||||
timePoints.append((completedUnitCount, timeStamp))
|
||||
if timePoints.count > timePointMaxCount { timePoints.removeFirst() }
|
||||
} else {
|
||||
timePoints.append((completedUnitCount, timeStamp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CancelBookDownloadOperation: Operation {
|
||||
|
@ -70,6 +70,11 @@ class LocalizedStrings {
|
||||
class var Library: String {return NSLocalizedString("Library", comment: "OS X, Preference")}
|
||||
class var ZimFiles: String {return NSLocalizedString("Zim Files", comment: "OS X, Preference")}
|
||||
|
||||
class Common {
|
||||
private static let comment = "Common"
|
||||
static let cancel = "Cancel"
|
||||
}
|
||||
|
||||
class LibraryTabTitle {
|
||||
private static let comment = "Library Tab Titles"
|
||||
static let cloud = "Cloud"
|
||||
|
Loading…
x
Reference in New Issue
Block a user