Logical fault in repeat-while loop? - swift

I have a problem with the repeat-while loop.I dont know why it doesnt work at my code.Maybe i have some logical fault.
func getJson(){
repeat{
movieName.isHidden = true
let randomNumber = Int(arc4random_uniform(128188))
let jsonUrlString = "https://api.url/3/movie/" + String(randomNumber) + "?api_key="
//let jsonUrlString = "https://api.url.org/3/movie/564?api_key=key&language=de-DE"
guard let url = URL(string: jsonUrlString) else
{return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
let dataString = String(data: data, encoding: .utf8)
print(dataString ?? String())
strTrue = false
Here I'm checking if the api returns some data if not they will give me a status code 34.Im checking for the word "status code".
If the api returns "status_code". I'm checking it with a if and then I turn string-code to true.So the condition of the repeat-while loop is
then string-code is true he will start it again and looking for a new movie id.But I tested it and it doesn't work. Maybe you guys can help me :)
let stringCode = dataString?.contains("status_code")
if stringCode == true {
print("yes fehler 34")
strTrue = stringCode!
print(strTrue)
}
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else {return}
let movie = Movie(json: json)
print(movie.name)
print(movie.genres)
let imageString = "https://image.url.org/t/p/w500"+(movie.imageUrl)
let url2 = URL(string: imageString)
//self.movieImage.downloadedFrom(url: url2!)
self.movieDescriptionLabel = movie.overview
self.movieNameLabel = movie.name
DispatchQueue.main.async {
self.movieName.isHidden = false
self.movieName.text = self.movieNameLabel
self.movieDescription.text = self.movieDescriptionLabel
self.movieImage.downloadedFrom(url: url2!)
}
} catch let jsonError {
print("Error",jsonError)
self.getJson()
}
}.resume()
}while(strTrue == true)

Related

Why is this URLSession.datatask not working in Swift 5 for macos

I am trying to make my own DynamicIP updater as a command line tool so I can set it up to run as a launch agent. I thought this would be a pretty simple thing to do, but I am not getting anything when I run this bit of code.
main.swift:
import AppKit
let userName = "yourUserName"
let password = "yourPassword"
let domain = "yourDomainName"
let ftp = "ftp"
let www = "www"
let checkIPURL = URL(string: "https://svc.joker.com/nic/checkip")
let domainUpdateURL = URL(string: "https://svc.joker.com/nic/update?username=\(userName)&password=\(password)&hostname=\(domain)")
let ftpUpdateURL = URL(string: "https://svc.joker.com/nic/update?username=\(userName)&password=\(password)&hostname=\(ftp).\(domain)")
let wwwUpdateURL = URL(string: "https://svc.joker.com/nic/update?username=\(userName)&password=\(password)&hostname=\(www).\(domain)")
var ipAddress = ""
if let url = checkIPURL {
print("1 - \(url)")
var request = URLRequest(url: url)
print("2 - \(request.url!)")
request.httpMethod = "POST"
print("3")
let session = URLSession.shared
print("4")
session.dataTask(with: request) { data, response, error in
print("4.1")
guard error == nil else {
print("Error:", error ?? "")
return
}
print("4.2")
guard (response as? HTTPURLResponse)?
.statusCode == 200 else {
print("down")
return
}
print("4.3")
if let data = data {
if let dataString = String(decoding: data, as: UTF8.self).removeHtmlTags() {
if let startIndex = dataString.lastIndex(of: " ") {
let chars = dataString.distance(from: startIndex, to: dataString.endIndex)-1
ipAddress = String(dataString.suffix(chars))
}
}
print(ipAddress)
} else {
print("No data")
}
print("up - \(response!)")
}.resume()
print("Done.")
}
extension String {
// Credit - Andrew - https://stackoverflow.com/questions/25983558/stripping-out-html-tags-from-a-string
func removeHtmlTags() -> String? {
do {
guard let data = self.data(using: .utf8) else {
return nil
}
let attributed = try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
return attributed.string
} catch {
return nil
}
}
}
Everything outside of the session prints, but nothing inside of it prints (4.x statements).
I deleted the AppSandbox because when I have AppSandbox as a Capability and turn on Outgoing Connections I get a crash with EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
But even with AppSandbox deleted it does not work.
The strange thing is this works fine in a playground (with a slight modification turning the String extension into a function within the playground), which really makes this a head scratcher for me.
Here's my playground code:
import AppKit
let userName = "yourUserName"
let password = "yourPassword"
let domain = "yourDomainName"
let ftp = "ftp"
let www = "www"
let checkIPURL = URL(string: "https://svc.joker.com/nic/checkip")
let domainUpdateURL = URL(string: "https://svc.joker.com/nic/update?username=\(userName)&password=\(password)&hostname=\(domain)")
let ftpUpdateURL = URL(string: "https://svc.joker.com/nic/update?username=\(userName)&password=\(password)&hostname=\(ftp).\(domain)")
let wwwUpdateURL = URL(string: "https://svc.joker.com/nic/update?username=\(userName)&password=\(password)&hostname=\(www).\(domain)")
var ipAddress = ""
if let url = checkIPURL {
print("1 - \(url)")
var request = URLRequest(url: url)
print("2 - \(request.url!)")
request.httpMethod = "POST"
print("3")
let session = URLSession.shared
print("4")
session.dataTask(with: request) { data, response, error in
print("4.1")
guard error == nil else {
print("Error:", error ?? "")
return
}
print("4.2")
guard (response as? HTTPURLResponse)?
.statusCode == 200 else {
print("down")
return
}
print("4.3")
if let data = data {
//if let dataString = String(decoding: data, as: UTF8.self).removeHtmlTags() {
if let dataString = removeHtmlTags(data: data) {
if let startIndex = dataString.lastIndex(of: " ") {
let chars = dataString.distance(from: startIndex, to: dataString.endIndex)-1
ipAddress = String(dataString.suffix(chars))
}
}
print(ipAddress)
} else {
print("No data")
}
print("up - \(response!)")
}.resume()
print("Done.")
}
func removeHtmlTags(data: Data) -> String? {
do {
let attributed = try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
return attributed.string
} catch {
return nil
}
}
Is there something else I need to do to get this to work within the command line tool app I am trying to build?

Multiple File Upload in Swift

I am trying to upload multiple file to the server by my iPhone. The problem is that when I try to upload 1.4 mb file it could not upload properly. I checked the uploaded file and the file size is 1 kb. So I made this code based on the postman. As you know the postman can create the code according to which language do you want. Thank you.
function uploadFile(){
var parameters: [[String: Any]] = []
var fileCount: Int = 0
let homeDirectory = ""
var soundPath = "\(homeDirectory)\(CommonUtil.PATH_SOUND)"
soundPath = soundPath.replacingOccurrences(of: "file:///", with: "")
//fiels[0] = ["fileName" : "2021_10_19_09_12_52.wav"]
//fiels[1] = ["fileName" : "2021_10_19_09_12_53.wav"]
//fiels[3] = ["fileName" : "2021_10_19_09_12_54.wav"]
for item in files{
var dict = item as! [String:String]
let strFilePath = "\(soundPath)\(dict["fileName"]!)"
if FileManager.default.fileExists(atPath: strFilePath){
var dict = [String:Any]()
dict.updateValue("strFileName[\(fileCount)]", forKey: "key")
dict.updateValue(strFilePath, forKey: "src")
dict.updateValue("file", forKey: "type")
parameters.append(dict)
fileCount++
}
print(dict["fileName"]!)
}
var dict = [String:Any]()
dict.updateValue("strApiName", forKey: "key")
dict.updateValue("soundFileUpload", forKey: "value")
dict.updateValue("text", forKey: "type")
parameters.append(dict)
uploadFiles(parameters: parameters)
}
func uploadFiles(parameters: [[String: Any]]){
var semaphore = DispatchSemaphore(value: 0)
let boundary = "Boundary - \(UUID().uuidString)"
var body = ""
var error: Error? = nil
for param in parameters {
if param["disabled"] == nil {
let paramName = param["key"]!
body += "--\(boundary)\r\n"
body += "Content-Disposition:form-data; name=\"\(paramName)\""
if param["contentType"] != nil {
body += "\r\nContent-Type: \(param["contentType"] as! String)"
}
let paramType = param["type"] as! String
if paramType == "text" {
let paramValue = param["value"] as! String
body += "\r\n\r\n\(paramValue)\r\n"
} else {
let paramSrc = param["src"] as! String
//let fileData = URL(string: paramSrc)?.dataRepresentation ?? Data()
let fileData = try! NSData(contentsOfFile:paramSrc, options:[]) as Data
let fileContent = String(data: fileData, encoding: .utf8)
body += "; filename=\"\("file:///"+paramSrc)\"\r\n"
+ "Content-Type: \"content-type header\"\r\n\r\n\(fileContent)\r\n"
}
}
}
body += "--\(boundary)--\r\n";
let postData = body.data(using: .utf8)
var request = URLRequest(url: URL(string: "http://api.polytus.com/")!,timeoutInterval: Double.infinity)
request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = postData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
DispatchQueue.main.async {
self.showLoading(show: false)
self.showBluetoothAlert(message: "failed")
}
print(String(describing: error))
semaphore.signal()
return
}
DispatchQueue.main.async {
self.showLoading(show: false)
self.showBluetoothAlert(message: "success")
}
print(String(data: data, encoding: .utf8)!)
semaphore.signal()
}
task.resume()
semaphore.wait()
}
For what I've understand you want to know the file size in the server filesystem. If this is you want you have to use an API that give you back the data, after the file is uploaded and moved from temporary web server space to final location. In brief you need to do 2 request, one for upload and one for getting the file data (space or any other data needed), or develop an API that give you as answer of the correct upload the file size.

Swift scraping a webpage using regex or alternative

See updates below first.
I am trying to scrape all the moderators for a specified sub-reddit on reddit.
The API only lets you get all the moderators usernames for a sub-reddit, so initially I had gotten all these and then performed an additional request for each of these profiles to get the avatar url. This ended up going past the API limit.
So instead I want to just get the source of the following page and paginate through while collecting the 10 usernames and avatar url's on each page. This will end up polling the website with less requests. I understand how to do the pagination part but for now I am trying to understand how to gather the usernames and adjoining avatar URLs.
So take the following url:
https://www.reddit.com/r/videos/about/moderators/
So I will pull the entire page source,
Add all the mods usernames & urls into a mod object, then into an array.
Would using regex on the string I get back be a good idea?
This is my code so far, any help would be great:
func tester() {
let url = URL(string: "https://www.reddit.com/r/videos/about/moderators")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print("\(error)")
return
}
let string = String(data: data, encoding: .utf8)
let regexUsernames = try? NSRegularExpression(pattern: "href=\"/user/[a-z0-9]\"", options: .caseInsensitive)
var results = regexUsernames?.matches(in: string as String, options: [], range: NSRange(location: 0, length: string.length))
let regexProfileURLs = try? NSRegularExpression(pattern: "><img src=\"[a-z0-9]\" style", options: .caseInsensitive)
print("\(results)") // This shows as empty array
}
task.resume()
}
I have also tried the following but get this error:
Can't form Range with upperBound < lowerBound
Code:
func tester() {
let url = URL(string: "https://www.reddit.com/r/videos/about/moderators")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print("data was nil")
return
}
guard let htmlString = String(data: data, encoding: .utf8) else {
print("cannot cast data into string")
return
}
let leftSideOfValue = "href=\"/user/"
let rightSideOfValue = "\""
guard let leftRange = htmlString.range(of: leftSideOfValue) else {
print("cannot find range left")
return
}
guard let rightRange = htmlString.range(of: rightSideOfValue) else {
print("cannot find range right")
return
}
let rangeOfTheValue = leftRange.upperBound..<rightRange.lowerBound
print(htmlString[rangeOfTheValue])
}
UPDATE:
So I have gotten to a point where it will give me the first username, however I am looping and just getting the same one, over and over. What would be the best way to move on each incremental step? Is there a way to do something like let newHTMLString = htmlString.dropFirst(k: ?) to replace the htmlString with a substring that is after the elements we just got?
func tester() {
let url = URL(string: "https://www.reddit.com/r/pics/about/moderators")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print("data was nil")
return
}
guard let htmlString = String(data: data, encoding: .utf8) else {
print("cannot cast data into string")
return
}
let counter = htmlString.components(separatedBy:"href=\"/user/")
let count = counter.count
for i in 0...count {
let leftSideOfUsernameValue = "href=\"/user/"
let rightSideOfUsernameValue = "\""
let leftSideOfAvatarURLValue = "><img src=\""
let rightSideOfAvatarURLValue = "\">"
guard let leftRange = htmlString.range(of: leftSideOfUsernameValue) else {
print("cannot find range left")
return
}
guard let rightRange = htmlString.range(of: rightSideOfUsernameValue) else {
print("cannot find range right")
return
}
let username = htmlString.slice(from: leftSideOfUsernameValue, to: rightSideOfUsernameValue)
print(username)
guard let avatarURL = htmlString.slice(from: leftSideOfAvatarURLValue, to: rightSideOfAvatarURLValue) else {
print("Error")
return
}
print(avatarURL)
}
}
task.resume()
}
I have also tried:
let endString = String(avatarURL + rightSideOfAvatarURLValue)
let endIndex = htmlString.index(endString.endIndex, offsetBy: 0)
let substringer = htmlString[endIndex...]
htmlString = String(substringer)
You should be able to pull all names and urls into two separate arrays by calling a simple regex by doing something like:
func tester() {
let url = URL(string: "https://www.reddit.com/r/pics/about/moderators")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
guard let htmlString = String(data: data, encoding: .utf8) else { return }
let names = htmlString.matching(regex: "href=\"/user/(.*?)\"")
let imageUrls = htmlString.matching(regex: "><img src=\"(.*?)\" style")
print(names)
print(imageUrls)
}
task.resume()
}
extension String {
func matching(regex: String) -> [String] {
guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
let result = regex.matches(in: self, options: [], range: NSMakeRange(0, self.count))
return result.map {
return String(self[Range($0.range, in: self)!])
}
}
}
Or you can create an object for each of the <div class="_1sIhmckJjyRyuR_z7M5kbI"> and then grab the names and urls to use as required.

Core data how to use NSMangedObjectContext in multithreaded

Okay, I've been going at this for a day and can't seem to figure out what I am doing wrong. This is how my data model looks like for core data.
This is how my code looks like.
class Service {
static let shared = Service()
private let numberOfPokemons = 151
func downloadPokemonsFromServer(completion: #escaping ()->()) {
let urlString = "https://pokeapi.co/api/v2/pokemon?limit=\(numberOfPokemons)"
guard let url = URL(string: urlString) else { return }
var id: Int16 = 0
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
print("Unable to fetch pokemon", err)
}
guard let data = data else { return }
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let pokemonJSON = try decoder.decode(PokemonsJSON.self, from: data)
pokemonJSON.pokemons.forEach { (JSONPokemon) in
id += 1
let pokemon = Pokemon(context: privateContext)
pokemon.name = JSONPokemon.name
pokemon.url = JSONPokemon.detailUrl
pokemon.id = id
}
try? privateContext.save()
try? privateContext.parent?.save()
completion()
} catch let err {
print("Unable to decode PokemonJSON. Error: ",err)
completion()
}
}.resume()
}
private var detailTracker = 0
func fetchMoreDetails(objectID: NSManagedObjectID) {
guard let pokemon = CoreDataManager.shared.persistentContainer.viewContext.object(with: objectID) as? Pokemon, let urlString = pokemon.url else { return }
print(pokemon.name)
print()
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
print("Unable to get more details for pokemon", err)
}
guard let data = data else { return }
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let pokemonDetailJSON = try decoder.decode(PokemonDetailJSON.self, from: data)
pokemonDetailJSON.types.forEach { (nestedType) in
let type = Type(context: privateContext)
type.name = nestedType.type.name
type.addToPokemons(pokemon)
}
try? privateContext.save()
try? privateContext.parent?.save()
} catch let err {
print("Unable to decode pokemon more details", err)
}
}.resume()
}
private var imageTracker = 0
func getPokemonImage(objectID: NSManagedObjectID) {
guard let pokemon = CoreDataManager.shared.persistentContainer.viewContext.object(with: objectID) as? Pokemon else { return }
let id = String(format: "%03d", pokemon.id)
let urlString = "https://assets.pokemon.com/assets/cms2/img/pokedex/full/\(id).png"
print(urlString)
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
print("Unable to load image from session.", err)
}
guard let data = data else { return }
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
pokemon.image = data
self.imageTracker += 1
if self.imageTracker == self.numberOfPokemons {
try? privateContext.save()
try? privateContext.parent?.save()
}
}.resume()
}
}
I have 3 entities, which are Pokemon, Type & Ability. I am not doing nothing with ability right now, so we can just ignore that. The first func downloadPokemonFromServer just grabs the first 151 pokemon, saves the name and a url of pokemon. I then use that url to go into another URLSession and grab more information about that pokemon. Which is what the fetchMoreDetails func does. However, this func crashes my app. I don't know what I am doing wrong here, it crashes when I try to save it.
The third func getPokemonImage I go into another URLSession, get the data and save it to my pokemon image attribute. The thing is this works perfectly fine. It saves to my CoreData and it doesn't crash my app.
This is how I call it in my ViewController.
#objc func handleRefresh() {
if pokemonController.fetchedObjects?.count == 0 {
Service.shared.downloadPokemonsFromServer {
let pokemons = self.pokemonController.fetchedObjects
pokemons?.forEach({ (pokemon) in
Service.shared.getPokemonImage(objectID: pokemon.objectID)
//If I uncomment the line below it will crash my app.
//Service.shared.fetchMoreDetails(objectID: pokemon.objectID)
})
}
}
tableView.refreshControl?.endRefreshing()
}
Will someone pls help me figure out what I am doing wrong. Would really appreciate the help.
You need to make sure you're doing all the Core Data work on the same thread as the private context you've created. To do so please use:
privateContext.perform {
//Core data work: create new entities, connections, delete, edit and more...
}
This can prevent you a lot of headaches and troubles down the road
I think the problem is that you are trying to set a relationship between two objects from different contexts. Your pokemon object is registered with the view context:
guard let pokemon = CoreDataManager.shared.persistentContainer.viewContext.object(with: objectID) as? Pokemon, let urlString = pokemon.url else { return }
whereas your type object is registered with the private context:
let type = Type(context: privateContext)
type.name = nestedType.type.name
so this line will not work:
type.addToPokemons(pokemon)
I would try amending the code to use only the privateContext, something like this:
func fetchMoreDetails(objectID: NSManagedObjectID) {
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
guard let pokemon = privateContext.object(with: objectID) as? Pokemon, let urlString = pokemon.url else { return }
print(pokemon.name)
print()
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
print("Unable to get more details for pokemon", err)
}
guard let data = data else { return }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let pokemonDetailJSON = try decoder.decode(PokemonDetailJSON.self, from: data)
pokemonDetailJSON.types.forEach { (nestedType) in
let type = Type(context: privateContext)
type.name = nestedType.type.name
type.addToPokemons(pokemon)
}
try? privateContext.save()
try? privateContext.parent?.save()
} catch let err {
print("Unable to decode pokemon more details", err)
}
}.resume()
}

NSURLSession validation

I'm trying o work out the correct (or at least a good) way to handle data request using Swift 2.
What i want to achieve is to:
Check if a data connection is available, if no then alert the user
If connection is available make a request
Check request response, if invalid then alert the user
if valid, check the response code and display one of 2 messages
what i have so far
let requestURL: String = **********************
let url = NSURL(string: requestURL)!
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let postParams: NSDictionary = ["id":newId!]
request.HTTPMethod = "POST"
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(postParams, options: [])
let jsonString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)! as String
let utf8str = jsonString.dataUsingEncoding(NSUTF8StringEncoding)
let base64Encoded = utf8str?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
let bodyData = "data="+base64Encoded!
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding)
} catch {
print("Error serialising JSON")
}
session.dataTaskWithRequest(request) { data, response, error in
// Make sure response is OK
guard let realResponse = response as? NSHTTPURLResponse where realResponse.statusCode == 200 else
{
// Show success message
print("Invalid Response from server")
}
// Read JSON Data
if let postString = NSString(data:data!, encoding: NSUTF8StringEncoding) as? String {
if let responseData = postString.dataUsingEncoding(NSUTF8StringEncoding) {
let json = JSON(data: responseData)
let responseMessage:String = json["response"]["data"].stringValue
if(responseMessage == "success")
{
print("successful post")
}else{
print("error saving post")
}
}
}
}
Currently if i was to run this in airplane mode then nothing happens, but i would like to catch this and tell these they need a data connection
Try sth like this:
func connectedToNetwork() -> Bool {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(&zeroAddress) {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}
var flags = SCNetworkReachabilityFlags()
if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
return false
}
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
return (isReachable && !needsConnection)
}
if(connectedToNetwork() == true){
//call your function to make the request
} else {
//show alert that they need an internet connection
}