// // 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 var excludedDates: [Date] = [] // EXDATE var isRecurring: Bool = false // If the event is recurring var frequency: String = "" // Frequence in case of recurring events, e.g. DAILY or WEEKLY var recCount: Int = 0 // How often the event occurs in case of recurring events var recInterval: Int = 0 // Interval of the recurring event, e.g. 1 for every week / day / ... var recDay: String = "" // The day of the recurrence, e.g. FR for friday var recUntil: Date = Date() // Until when the event has to be repeated } 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()) // If there are more than 2 lines that have to be merged, we need to find out how many lines we have to go up var goUp = 1 while(lines[lineNr-goUp] == ""){ goUp += 1 } lines[lineNr-goUp].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{ // Get known UIDs let existingEvents: [RaPlaEvent] = RaPlaEvent.getAll() var existingEventsDict: [String:RaPlaEvent] = [:] for event in existingEvents { existingEventsDict[event.uid!] = event } // List for new UIDs var newEventUIDs: [String] = [] for event in eventObjects { // If the event already exists locally, update it. Otherwise, create a new record if(event.isRecurring){ // Create as many events as we need // If we e.g. need 12 events, we create 0...11 for iteration in 0.. [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 } }