Skip to content

Commit

Permalink
Merge pull request #152 from GianniCarlo/fix/multiple-files
Browse files Browse the repository at this point in the history
Fix importing multiple files
  • Loading branch information
pichfl authored Jun 18, 2018
2 parents e127a6a + b2b243f commit 0d800ef
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 191 deletions.
23 changes: 2 additions & 21 deletions BookPlayer/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,27 +65,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey: Any] = [:]) -> Bool {
// This function is called when the app is opened with a audio file url,
// like when receiving files through AirDrop

let fmanager = FileManager.default
let filename = url.lastPathComponent
let documentsURL = fmanager.urls(for: .documentDirectory, in: .userDomainMask).first!
let destinationURL = documentsURL.appendingPathComponent(filename)

// move file from Inbox to Document folder
do {
try fmanager.moveItem(at: url, to: destinationURL)
// In case the app was already running in background
let userInfo = ["fileURL": destinationURL]
NotificationCenter.default.post(name: Notification.Name.AudiobookPlayer.openURL, object: nil, userInfo: userInfo)
} catch {
do {
try fmanager.removeItem(at: url)
} catch {
// @TODO: How should this case be handled?
}

return false
}
let userInfo = ["fileURL": url]
NotificationCenter.default.post(name: Notification.Name.AudiobookPlayer.libraryOpenURL, object: nil, userInfo: userInfo)

return true
}
Expand Down
5 changes: 3 additions & 2 deletions BookPlayer/Extensions/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import UIKit

extension Notification.Name {
public struct AudiobookPlayer {
public static let openURL = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.openurl")
public static let libraryOpenURL = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.library.openurl")
public static let playlistOpenURL = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.playlist.openurl")
public static let requestReview = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.requestreview")
public static let updatePercentage = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.book.percentage")
public static let updateChapter = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.book.chapter")
Expand All @@ -22,7 +23,7 @@ extension Notification.Name {
public static let bookChange = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.book.change")
public static let bookPlaying = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.book.playback")
public static let skipIntervalsChange = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.settings.skip")
public static let bookDeleted = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.book.deleted")
public static let reloadData = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.reloaddata")
public static let playerPresented = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.player.presented")
public static let playerDismissed = Notification.Name(rawValue: "com.tortugapower.audiobookplayer.player.dismissed")
}
Expand Down
67 changes: 26 additions & 41 deletions BookPlayer/Library/BaseListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ class BaseListViewController: UIViewController {

providerList.delegate = self

if #available(iOS 11.0, *) {
providerList.allowsMultipleSelection = true
}

self.present(providerList, animated: true, completion: nil)
}

Expand Down Expand Up @@ -164,6 +168,25 @@ class BaseListViewController: UIViewController {

cell.progress = progress
}

@objc func openURL(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let fileURL = userInfo["fileURL"] as? URL else {
return
}

MBProgressHUD.showAdded(to: self.view, animated: true)

let destinationFolder = DataManager.getProcessedFolderURL()

DataManager.processFile(at: fileURL, destinationFolder: destinationFolder) { (bookUrl) in
guard let bookUrl = bookUrl else {
MBProgressHUD.hideAllHUDs(for: self.view, animated: true)
return
}
self.loadFile(urls: [bookUrl])
}
}
}

extension BaseListViewController: UITableViewDataSource {
Expand Down Expand Up @@ -290,47 +313,9 @@ extension BaseListViewController: TableViewReorderDelegate {

extension BaseListViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else {
return
}

// @TODO: Consider importing multiple files at once

self.addFileFromUrl(url)
}

func addFileFromUrl(_ url: URL) {
// Documentation states that the file might not be imported due to being accessed from somewhere else

do {
try FileManager.default.attributesOfItem(atPath: url.path)
} catch {
self.showAlert("Error", message: "There was an error reading the file, please try again.")

return
}

let trueName = url.lastPathComponent
var finalPath = self.documentsPath + "/" + (trueName)

if trueName.contains(" ") {
finalPath = finalPath.replacingOccurrences(of: " ", with: "_")
}

let fileURL = URL(fileURLWithPath: finalPath.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!)

do {
try FileManager.default.moveItem(at: url, to: fileURL)
} catch {
self.showAlert("Error", message: "There was an error importing the file, please try again.")

return
}

MBProgressHUD.showAdded(to: self.view, animated: true)

DataManager.processPendingFiles { (urls) in
self.loadFile(urls: urls)
for url in urls {
let userInfo = ["fileURL": url]
NotificationCenter.default.post(name: Notification.Name.AudiobookPlayer.libraryOpenURL, object: nil, userInfo: userInfo)
}
}
}
79 changes: 38 additions & 41 deletions BookPlayer/Library/DataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,57 +94,45 @@ class DataManager {
- Parameter destinationFolder: File final location
- Returns: `URL` of the file's new location. Returns `nil` if hashing fails.
*/
internal class func processFile(at origin: URL, destinationFolder: URL) -> URL? {

guard let data = FileManager.default.contents(atPath: origin.path),
let digest = Digest(algorithm: .md5).update(data: data)?.final() else {
return nil
class func processFile(at origin: URL, destinationFolder: URL, completion:@escaping (URL?) -> Void) {
guard FileManager.default.fileExists(atPath: origin.path),
let inputStream = InputStream(url: origin) else {
completion(nil)
return
}

let hash = hexString(fromArray: digest)
let ext = origin.pathExtension
let filename = hash + ".\(ext)"
let destinationURL = destinationFolder.appendingPathComponent(filename)
DispatchQueue.global().async {
inputStream.open()

do {
if !FileManager.default.fileExists(atPath: destinationURL.path) {
try FileManager.default.moveItem(at: origin, to: destinationURL)
} else {
try FileManager.default.removeItem(at: origin)
}
} catch {
fatalError("Fail to move file from \(origin) to \(destinationURL)")
}
let digest = Digest(algorithm: .md5)

return destinationURL
}
while inputStream.hasBytesAvailable {
var inputBuffer = [UInt8](repeating: 0, count: 1024)
inputStream.read(&inputBuffer, maxLength: inputBuffer.count)
_ = digest.update(byteArray: inputBuffer)
}

/**
Process all the files in a folder and move them to the 'processed' folder (specified by the DataManager).
- Parameter folder: The folder which contents will be processed
- Parameter completion: Closure block which returns the array of new urls of the processed files
*/
internal class func processFiles(in folder: URL, completion:@escaping ([URL]) -> Void) {
var bookUrls = [URL]()
inputStream.close()

// Get reference of all the files located inside the Documents folder
guard let urls = self.getFiles(from: folder) else {
return completion(bookUrls)
}
let finalDigest = digest.final()

DispatchQueue.global().async {
// Iterate and process files
let destinationFolder = self.getProcessedFolderURL()
let hash = hexString(fromArray: finalDigest)
let ext = origin.pathExtension
let filename = hash + ".\(ext)"
let destinationURL = destinationFolder.appendingPathComponent(filename)

for fileURL in urls {
guard let bookUrl = self.processFile(at: fileURL, destinationFolder: destinationFolder) else {
continue
do {
if !FileManager.default.fileExists(atPath: destinationURL.path) {
try FileManager.default.moveItem(at: origin, to: destinationURL)
} else {
try FileManager.default.removeItem(at: origin)
}
bookUrls.append(bookUrl)
} catch {
fatalError("Fail to move file from \(origin) to \(destinationURL)")
}

DispatchQueue.main.async {
completion(bookUrls)
completion(destinationURL)
}
}
}
Expand All @@ -153,9 +141,18 @@ class DataManager {
Process all the files in the documents folder and move them to the 'processed' folder (specified by the DataManager).
- Parameter completion: Closure block which returns the array of new urls of the processed files
*/
class func processPendingFiles(completion:@escaping ([URL]) -> Void) {
class func notifyPendingFiles() {
let documentsFolder = self.getDocumentsFolderURL()
self.processFiles(in: documentsFolder, completion: completion)

// Get reference of all the files located inside the folder
guard let urls = self.getFiles(from: documentsFolder) else {
return
}

for url in urls {
let userInfo = ["fileURL": url]
NotificationCenter.default.post(name: Notification.Name.AudiobookPlayer.libraryOpenURL, object: nil, userInfo: userInfo)
}
}

// MARK: - Models handler
Expand Down
31 changes: 3 additions & 28 deletions BookPlayer/Library/LibraryViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ class LibraryViewController: BaseListViewController, UIGestureRecognizerDelegate
self.navigationController!.interactivePopGestureRecognizer!.delegate = self

// register for appDelegate openUrl notifications
NotificationCenter.default.addObserver(self, selector: #selector(self.openURL(_:)), name: Notification.Name.AudiobookPlayer.openURL, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.reloadData), name: Notification.Name.AudiobookPlayer.bookDeleted, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.openURL(_:)), name: Notification.Name.AudiobookPlayer.libraryOpenURL, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.reloadData), name: Notification.Name.AudiobookPlayer.reloadData, object: nil)

self.loadLibrary()

Expand Down Expand Up @@ -54,30 +54,13 @@ class LibraryViewController: BaseListViewController, UIGestureRecognizerDelegate
* Spaces in file names can cause side effects when trying to load the data
*/
func loadLibrary() {
//load local files
let loadingWheel = MBProgressHUD.showAdded(to: self.view, animated: true)
loadingWheel?.labelText = "Loading Books"

self.library = DataManager.getLibrary()

//show/hide instructions view
self.emptyLibraryPlaceholder.isHidden = !self.items.isEmpty
self.tableView.reloadData()

DataManager.processPendingFiles { (urls) in
guard !urls.isEmpty else {
MBProgressHUD.hideAllHUDs(for: self.view, animated: true)
return
}

DataManager.insertBooks(from: urls, into: self.library) {
MBProgressHUD.hideAllHUDs(for: self.view, animated: true)

//show/hide instructions view
self.emptyLibraryPlaceholder.isHidden = !self.items.isEmpty
self.tableView.reloadData()
}
}
DataManager.notifyPendingFiles()
}

override func loadFile(urls: [URL]) {
Expand All @@ -88,14 +71,6 @@ class LibraryViewController: BaseListViewController, UIGestureRecognizerDelegate
}
}

@objc func openURL(_ notification: Notification) {
MBProgressHUD.showAdded(to: self.view, animated: true)

DataManager.processPendingFiles { (urls) in
self.loadFile(urls: urls)
}
}

@objc func reloadData() {
self.tableView.reloadData()
}
Expand Down
22 changes: 15 additions & 7 deletions BookPlayer/Library/PlaylistViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,19 @@ class PlaylistViewController: BaseListViewController {
override func viewDidLoad() {
super.viewDidLoad()

if !self.items.isEmpty {
self.emptyPlaylistPlaceholder.isHidden = true
}
self.emptyPlaylistPlaceholder.isHidden = !self.items.isEmpty

self.navigationItem.title = playlist.title

NotificationCenter.default.addObserver(self, selector: #selector(self.openURL(_:)), name: Notification.Name.AudiobookPlayer.playlistOpenURL, object: nil)
}

override func loadFile(urls: [URL]) {
DataManager.insertBooks(from: urls, into: self.playlist) {
MBProgressHUD.hideAllHUDs(for: self.view, animated: true)
NotificationCenter.default.post(name: Notification.Name.AudiobookPlayer.bookDeleted, object: nil)
NotificationCenter.default.post(name: Notification.Name.AudiobookPlayer.reloadData, object: nil)
self.tableView.reloadData()
self.emptyPlaylistPlaceholder.isHidden = !self.items.isEmpty
MBProgressHUD.hideAllHUDs(for: self.view, animated: true)
}
}

Expand All @@ -57,6 +58,13 @@ class PlaylistViewController: BaseListViewController {

bookCell.artworkButton.setImage(#imageLiteral(resourceName: "playerIconPause"), for: .normal)
}

override func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
for url in urls {
let userInfo = ["fileURL": url]
NotificationCenter.default.post(name: Notification.Name.AudiobookPlayer.playlistOpenURL, object: nil, userInfo: userInfo)
}
}
}

extension PlaylistViewController {
Expand Down Expand Up @@ -123,7 +131,7 @@ extension PlaylistViewController {
self.tableView.deleteRows(at: [indexPath], with: .none)
self.tableView.endUpdates()

NotificationCenter.default.post(name: Notification.Name.AudiobookPlayer.bookDeleted, object: nil)
NotificationCenter.default.post(name: Notification.Name.AudiobookPlayer.reloadData, object: nil)
}))

sheet.addAction(UIAlertAction(title: "Delete completely", style: .destructive, handler: { _ in
Expand All @@ -142,7 +150,7 @@ extension PlaylistViewController {
self.tableView.deleteRows(at: [indexPath], with: .none)
self.tableView.endUpdates()

NotificationCenter.default.post(name: Notification.Name.AudiobookPlayer.bookDeleted, object: nil)
NotificationCenter.default.post(name: Notification.Name.AudiobookPlayer.reloadData, object: nil)
} catch {
self.showAlert("Error", message: "There was an error deleting the file, please try again.")
}
Expand Down
4 changes: 4 additions & 0 deletions BookPlayer/Models/Playlist+CoreDataClass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public class Playlist: LibraryItem {
totalProgress += book.currentTime
}

guard totalDuration > 0 else {
return 0.0
}

return totalProgress / totalDuration
}

Expand Down
Loading

0 comments on commit 0d800ef

Please sign in to comment.