// // RaPlaFetcher.swift // DHBW-Service // // Created by Patrick Müller on 29.01.21. // import Foundation import CoreData class RaPlaFetcher { public class iCalEvent { var startDate: Date = Date() //DTSTART var endDate: Date = Date() //DTEND var summary: String = "" //SUMMARY var description: String = "" //DESCRIPTION var location: String = "" //LOCATION var category: String = "" //CATEGORIES var uid: String = "" //UID var lecturers: [LecturerObj] = [] //ATTENDEE } public class LecturerObj { var name: String = "" var email: String = "" } // Get the RaPla file from the given URL and save the events to CoreData public class func getRaplaFileAndSaveToCoreData(from urlString: String) -> Bool { let file = getFileAsString(from: urlString) let eventStrings = splitIntoEvents(file: file) let eventObjects = convertStringsToObjects(eventStrings: eventStrings) return saveToCoreData(eventObjects: eventObjects) } // Get the RaPla files from the given URL and return the event objects public class func getRaplaFileAndReturnEvents(from urlString: String) -> [iCalEvent] { let file = getFileAsString(from: urlString) let eventStrings = splitIntoEvents(file: file) return convertStringsToObjects(eventStrings: eventStrings) } // GET the file from the given URL and convert it to a String that is then returned private class func getFileAsString(from urlString: String) -> String { let url = URL(string: urlString)! var file: String = "" do { file = try String(contentsOf: url, encoding: .utf8) } catch let error { print(error.localizedDescription) } return file } // Split the given ical file string into individual event strings and return them as a list private class func splitIntoEvents(file: String) -> [String] { let regexOptions: NSRegularExpression.Options = [.dotMatchesLineSeparators] // Regex explanation: Matches BEGIN:VEVENT "Any character" END:VEVENT across multiple lines. The *? assures that we receive the // maximum amount of matches, i.e. it makes the regex non-greedy as we would otherwise just receive one giant match let eventStrings = UtilityFunctions.regexMatches(for: "BEGIN:VEVENT.*?END:VEVENT", with: regexOptions, in: file) return eventStrings } // Convert an ical event String into an iCalEvent Codable object as defined above private class func convertStringsToObjects(eventStrings: [String]) -> [iCalEvent] { var events: [iCalEvent] = [] for eventString in eventStrings { var lines = eventString.components(separatedBy: .newlines) // Remove all blank lines that somehow are generated by the .components function lines = removeBlankLines(lines: lines) let evt = iCalEvent() // Iterate over all lines and merge lines that have been split by rapla first for lineNr in 0...lines.count-1 { if(lines[lineNr].hasPrefix(" ")){ lines[lineNr] = String(lines[lineNr].dropFirst()) lines[lineNr-1].append(lines[lineNr]) lines[lineNr] = "" } } // Remove all blank lines again as we produced some while merging the lines lines = removeBlankLines(lines: lines) for line in lines { var lineWithoutPrefix = line if(!line.contains(":")) { continue } lineWithoutPrefix.removeSubrange(lineWithoutPrefix.startIndex...lineWithoutPrefix.firstIndex(of: ":")!) if(line.hasPrefix("DTSTART")){ //Date format: 20181101T080000 let dateFormatter = DateFormatter() if(lineWithoutPrefix.contains("Z")){ dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'" } else { dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss" } let date = dateFormatter.date(from: lineWithoutPrefix)! evt.startDate = date } else if(line.hasPrefix("DTEND")){ let dateFormatter = DateFormatter() if(lineWithoutPrefix.contains("Z")){ dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'" } else { dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss" } let date = dateFormatter.date(from: lineWithoutPrefix)! evt.endDate = date } else if(line.hasPrefix("SUMMARY")){ evt.summary = lineWithoutPrefix } else if(line.hasPrefix("DESCRIPTION")){ evt.description = lineWithoutPrefix } else if(line.hasPrefix("LOCATION")){ evt.location = lineWithoutPrefix } else if(line.hasPrefix("CATEGORIES")){ evt.category = lineWithoutPrefix } else if(line.hasPrefix("UID")){ evt.uid = lineWithoutPrefix } else if(line.hasPrefix("ATTENDEE;ROLE=REQ-PARTICIPANT;")) { var lecturerName = line let begin = lecturerName.firstIndex(of: "\"")! lecturerName.removeSubrange(lecturerName.startIndex...begin) let end = lecturerName.lastIndex(of: "\"")! lecturerName = String(lecturerName[.. Bool{ let existingEvents: [RaPlaEvent] = RaPlaEvent.getAll() var existingEventsDict: [String:RaPlaEvent] = [:] for event in existingEvents { existingEventsDict[event.uid!] = event } let newEventUIDs = eventObjects.map{$0.uid} for event in eventObjects { // If the event already exists locally, update it. Otherwise, create a new record let evt: RaPlaEvent if existingEventsDict.keys.contains(event.uid) { evt = existingEventsDict[event.uid]! } else { evt = RaPlaEvent(context: PersistenceController.shared.context) // Set default values for new object evt.isHidden = false } evt.startDate = event.startDate evt.endDate = event.endDate evt.summary = event.summary evt.descr = event.description evt.location = event.location evt.category = event.category evt.uid = event.uid for lecturer in event.lecturers { let lect = Lecturer(context: PersistenceController.shared.context) lect.name = lecturer.name lect.email = lecturer.email lect.event = evt } } // Now we also have to delete locally stored events that have been deleted from RaPla for localUid in existingEventsDict.keys { if(!newEventUIDs.contains(localUid)){ // Locally stored event does not exist in RaPla anymore, delete it let evt = existingEventsDict[localUid] PersistenceController.shared.context.delete(evt!) } } PersistenceController.shared.save() return true } private class func removeBlankLines(lines: [String]) -> [String] { var newLines = lines // Remove all blank lines that somehow are generated by the .components function for line in newLines { if(line.isEmpty){ newLines.remove(at: newLines.firstIndex(of: line)!) } } return newLines } }