iOS Content Blocking Extension Loading Multiple JSON Files - ios

Is it possible to return multiple JSON files from a Content Blocker Extension? In my UI users enable / disable different filters and each filter is represented by a separate file. I currently have (which only loads one despite iterating through multiple):
func beginRequestWithExtensionContext(context: NSExtensionContext) {
var items = Array <NSExtensionItem>()
let resources = ["a", "b", "c"]
for resource in resources {
let url = NSBundle.mainBundle().URLForResource(resource, withExtension: "json")
if let attachment = NSItemProvider(contentsOfURL: url) {
let item = NSExtensionItem()
item.attachments = [attachment]
items.append(item)
}
}
context.completeRequestReturningItems(items, completionHandler: nil)
}
I've tried doing multiple items and a single item with multiple attachments. If it isn't possible to have separate files, any way to combine multiple (or generate programmatically)?

It is possible to have multiple JSON files and use it for the Content Blocker extension.
1) Throws SFContentBlockerErrorDomain when you pass multiple extension items to completeRequestReturningItems method.
2) Can't attach multiple attachments to NSExtension. The comment on the source code says, the attachment is not meant to be an array of alternate data formats/types, but instead a collection to include in a social media post for example. These items are always typed NSItemProvider. I reckon you wouldn't be able to add multiple JSON data as attachments, since they are not a series of attachments to create a message.
My Solution (Verified it works):
NSItemProvider can be initialised with item (NSData) and typeIdentifier.
let aData = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("a", withExtension: "json")!)
let bData = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("b", withExtension: "json")!)
aJSON = `convert aData to JSON`
bJSON = `convert bData to JSON`
combinedJSON = `aJSON + bJSON`
combinedData = 'convert combinedJSON to NSData'
let attachment = NSItemProvider(item: combinedData, typeIdentifier: kUTTypeJSON as String)
Now you could create the extension with the attachment, combinedData as per your preferences.

For those curious I ended up adding code to dynamically generate a JSON file (persisted to disk). From other answers it seems like the step of saving could be avoided by returning an NSData representation of the file instead - although that attempt failed for me. Here's my snippet:
import UIKit
import MobileCoreServices
class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
func beginRequestWithExtensionContext(context: NSExtensionContext) {
let item = NSExtensionItem()
let items = [item]
let url = buildJSONFileURL()
if let attachment = NSItemProvider(contentsOfURL: url) { item.attachments = [attachment] }
context.completeRequestReturningItems(items, completionHandler: nil)
}
func buildJSONFileURL() -> NSURL {
let directories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let directory = directories[0]
let path = directory.stringByAppendingFormat("/block.json")
let selector = [...] // Dynamically Generated
let dictionary = [[
"action": [ "type": "css-display-none", "selector": selector ],
"trigger": [ "url-filter": ".*" ]
]]
let data = try! NSJSONSerialization.dataWithJSONObject(dictionary, options: NSJSONWritingOptions.PrettyPrinted)
let text = NSString(data: data, encoding: NSASCIIStringEncoding)!
try! text.writeToFile(path, atomically: true, encoding: NSASCIIStringEncoding)
return NSURL(fileURLWithPath: path)
}
}

You can combine two JSON rule files in to one file and use that file.
import UIKit
import MobileCoreServices
class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "you app group identifier")
let sourceURLRules = sharedContainerURL?.appendingPathComponent("Rules1.json")
let sourceURLRules2 = sharedContainerURL?.appendingPathComponent("Rules2.json")
do {
let jsonDecoder = JSONDecoder()
let dataFormRules1 = try Data(contentsOf: sourceURLRules1!, options: .mappedIfSafe)// Rule is Decode able Swift class
let rulesArray1 = try? jsonDecoder.decode(Array<Rule>.self,from: dataFormRules1)
let dataFormRules2 = try Data(contentsOf: sourceURLRules2!, options: .mappedIfSafe)
let rulesArray2 = try? jsonDecoder.decode(Array<Rule>.self,from: dataFormRules2)
saveCombinedRuleFile(ruleList: rulesArray1! + rulesArray2!)
} catch {
//handle error condition
}
let sourceURLCombinedRule = sharedContainerURL?.appendingPathComponent("CombinedRule.json")
let combinedRuleAttachment = NSItemProvider(contentsOf: sourceURLCombinedRule)
let item = NSExtensionItem()
item.attachments = [combinedRuleAttachment]
context.completeRequest(returningItems: [item], completionHandler: nil)
}
func saveCombinedRuleFile(ruleList:[Rule]) {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(ruleList) {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "you app group identifier")
if let json = String(data: encoded, encoding: .utf8) {
print(json)
}
if let destinationURL = sharedContainerURL?.appendingPathComponent("CombinedRule.json") {
do {
try encoded.write(to: destinationURL)
} catch {
print ("catchtry")
}
}
}
}
}

Related

Is there a way to prevent NSFilemanger from exporting data and creating a folder?

Whenever the user exports data (it is a JSON format) it creates a folder named "Documents." And does not directly export the file and it does this for both my JSON export and CSV.
I have tried changing the fileManager settings but nothing seems to work that I have tried and that includes changing the default, for, and in. The latter two being when I call fileManger.url
Here is my main export for my JSON
// MARK: - Export to Share
func toExportJSON() {
// Call to clear Chached exported files
clearAllFile()
var exportArrayJson = [exportJsonData]()
var exportArray = [Item]()
exportArray = fetchedRC.fetchedObjects!
for i in exportArray {
let newI = exportJsonData(name: i.name!, pricePer: i.pricePer, totalPrice: i.totalPrice!, isComplete: i.isComplete, Qty: i.quantity, Cat: i.catagory!, Priority: i.priority, DNH: i.didHave)
exportArrayJson.append(newI)
}
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.keyEncodingStrategy = .convertToSnakeCase
var jsonDataTop = Data()
do {
let jsonData = try encoder.encode(exportArrayJson)
jsonDataTop = jsonData
// Old Code to print for testing
///if let jsonString = String(data: jsonData, encoding: .utf8) {
///print(jsonString)
///print(jsonData)
///}
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
let fileURL = path.appendingPathComponent("\(detailedList.lname!).json")
try jsonDataTop.write(to: fileURL)//.write(to: fileURL)//write(to: fileURL, encoding: .utf8)
let vc = UIActivityViewController(activityItems: [path], applicationActivities: [])
present(vc, animated: true, completion: nil)
} catch {
print("error creating file")
}
} catch {
print(error.localizedDescription)
}
}
I expect this to export the JSON by its self and not in a folder. I want only the file so it is easier for the user to share.
No matter where you save the file, you should delete it when the UIActivityViewController is finished. You can set a completion handler to remove the file.
vc.completionWithItemsHandler = { (_, _, _, _) in
try? fileManager.removeItem(at: fileURL)
}
Another option would be to save the file in the temporaryDirectory instead of documents.
let fileURL = path
.temporaryDirectory
.appendingPathComponent("\(detailedList.lname!).json")
But you should still remove the file after.

Swift get all items from URL

I am making small music player with mp3 ftp steam.
It's how i add my URL's to list:
self.playList.add("http://my-ftp.pl/mp3/1.mp3")
self.playList.add("http://my-ftp.pl/mp3/2.mp3")
self.playList.add("http://my-ftp.pl/mp3/3.mp3")
...
self.play(url: URL(string:(playList[self.index] as! String))!)
I am searching for any ideas how to get all (*.mp3) URL (http://my-ftp.pl/mp3) folder elements without entering the entire path.
There is no way to get contents of a remote folder , but you can
var counter = 1
func getMp3(_ url:URL) {
API.load(url) { (response,data,error) in
if let data = data {
// use data
coounter += 1
getMp3(urlWithNewCounter)
}
}
Sorry guys, but i made that.
func getMp3() {
let url = URL(string: "http://not-my-ftp-just-simple.com/mp3")!
let task = URLSession.shared.downloadTask(with: url) { localURL, urlResponse, error in
if let localURL = localURL {
if let string = try? String(contentsOf: localURL) {
let regex = try! NSRegularExpression(pattern:"href=(.*?)>", options: [])
var results = [String]()
regex.enumerateMatches(in: string, options: [], range: NSMakeRange(0, string.utf16.count)) { result, flags, stop in
if let r = result?.range(at: 1), let range = Range(r, in: string) {
if (String(String(string[range]).toLengthOf(length: 1).dropLast())).contains(".mp3"){
results.append(String(String(string[range]).toLengthOf(length: 1).dropLast()))
}
}
}
print(results)
}
}
}
task.resume()
}

Using multiple JSON lists in iOS Content Blocker

I am trying to create my own content blocker on iOS. I was wanting to create separate json lists for different types of content (tracking, ads, adult sites, etc). I came across this https://github.com/calebhicks/ios-safari-content-blocking which stated you could create an array of "attachments" instead of relying on the singular "blockerList" json file.
func beginRequest(with context: NSExtensionContext) {
var jsonFiles:Array<NSItemProvider> = Array()
let attachment = NSItemProvider(contentsOf: Bundle.main.url(forResource: "blockerList", withExtension: "json"))!
jsonFiles.append(attachment)
let attachment2 = NSItemProvider(contentsOf: Bundle.main.url(forResource: "testList", withExtension: "json"))!
jsonFiles.append(attachment2)
let item = NSExtensionItem()
item.attachments = jsonFiles
context.completeRequest(returningItems: [item], completionHandler: nil)
}
Most of this code is the default from the Content Blocker Extension setup, but what I have added is the jsonFiles array which attachment and attachment2 are placed in. When this is run, only one of the two rule sets is loaded, never a combination of the two. Any ideas on why only one ruleset is loaded?
You can combine two JSON rule files in to one file and use that file.
import UIKit
import MobileCoreServices
class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "you app group identifier")
let sourceURLRules = sharedContainerURL?.appendingPathComponent("Rules1.json")
let sourceURLRules2 = sharedContainerURL?.appendingPathComponent("Rules2.json")
do {
let jsonDecoder = JSONDecoder()
let dataFormRules1 = try Data(contentsOf: sourceURLRules1!, options: .mappedIfSafe)// Rule is Decode able Swift class
let rulesArray1 = try? jsonDecoder.decode(Array<Rule>.self,from: dataFormRules1)
let dataFormRules2 = try Data(contentsOf: sourceURLRules2!, options: .mappedIfSafe)
let rulesArray2 = try? jsonDecoder.decode(Array<Rule>.self,from: dataFormRules2)
saveCombinedRuleFile(ruleList: rulesArray1! + rulesArray2!)
} catch {
//handle error condition
}
let sourceURLCombinedRule = sharedContainerURL?.appendingPathComponent("CombinedRule.json")
let combinedRuleAttachment = NSItemProvider(contentsOf: sourceURLCombinedRule)
let item = NSExtensionItem()
item.attachments = [combinedRuleAttachment]
context.completeRequest(returningItems: [item], completionHandler: nil)
}
func saveCombinedRuleFile(ruleList:[Rule]) {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(ruleList) {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "you app group identifier")
if let json = String(data: encoded, encoding: .utf8) {
print(json)
}
if let destinationURL = sharedContainerURL?.appendingPathComponent("CombinedRule.json") {
do {
try encoded.write(to: destinationURL)
} catch {
print ("catchtry")
}
}
}
}
}

swift 3.1 reading CSV or PLIST file from web

I'd like to use readStringFromURL method to obtain a file from a plist and then use it on insertDataInArrayFromPlist in order to display it or put it on CoreData, substituting let path = Bundle.main.path(forResource: plistFileName, ofType: plistFileExtension).
the ISSUE the try statement gives me this ERROR
Argument labels '(contentsOfURL:, usedEncoding:)' do not match any available overloads
in my viewDidLoad:
let obtainedfile = readStringFromURL(stringURL: kremoteSamplePlist)
print(obtainedfile ?? "nothing to print")
I retrive the file from web
func readStringFromURL(stringURL:String)-> String!{
if let url = NSURL(string: stringURL) {
do {
return try String(contentsOfURL: url, usedEncoding: nil)
} catch {
print("Cannot load contents")
return nil
}
} else {
print("String was not a URL")
return nil
}
}
then I put the data in a struct
func insertDataInArrayFromPlist(arrayOfEntities: inout [product]) {
let path = Bundle.main.path(forResource: plistFileName, ofType: plistFileExtension)
let localArray = NSArray(contentsOfFile: path!)!
for dict in localArray {
var futureEntity = product()
let bdict = dict as! [String: AnyObject]
futureEntity.name = bdict["Name"] as? String
futureEntity.ProductId = bdict["Product Id"] as? String
arrayOfEntities.append(futureEntity)
}
for element in arrayOfEntities {
print("name is \(element.name!), the id is \(element.ProductId!)")
}
}
Theres a library available via Cocoapods, CSV.swift by Yaslab. Allows you to import a csv directly in Swift code and convert to a data type of your own. Does the job for me.
https://github.com/yaslab/CSV.swift

How to save/retrieve dictionary from file using SwiftyJSON

I'm trying to make the conversion from Objc to swift and have had better days.
I have a class with a dictionary:
collaborationDictionary:[String:Set<String>]
I am trying to write/read this dictionary to/from a file and just can't quite seem to make it work. I have to save the dictionary using the following JSON structure and I have to use SwiftyJSON.
{ "Collaborations" : {
"5604" : [
"whiteboard.png",
"VID_20161123_135117.3gp",
"Photo_0.jpeg"]
"5603" : [
"VID_20161123_135117.3gp"],
"5537" : [
"Screenshot_20151212-132454.png",
"VID_20161202_083205.3gp",
"VID_20161123_135117.3gp",
"Photo_0.jpeg",
"Screenshot_20151212-132428.png",
"Screenshot_20151212-132520.png",
"IMG_20161017_132105.jpg",
"whiteboard.png"]}
}
I don't have any real problem with finding/retrieving the file or writing the file. I just can't quite figure out how to manually load SwiftyJSON. I need to have a JSON object called "Collaborations" at the top. It needs to contain a dictionary of collaboration IDs (5604, 5603...). Each collaboration contains an array of string (filenames). I'm including the code I'm using to read/write the file but I need help with the SwiftyJSON library.
This is the member data member I'm using to store the above data:
These are the functions I need to finish:
private var collaborationDictionary:[String:Set<String>] = [:]
func getUploadedFileSet() {
collaborationDictionary = [:]
let documentsURL = URL(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let appURL = documentsURL?.appendingPathComponent(APP_DISTINGUISHED_NAME)
let jsonFileURL = appURL?.appendingPathComponent(UPLOADED_ITEMS_DB_JSON)
if FileManager.default.fileExists(atPath: (jsonFileURL?.absoluteString)!) {
do {
let data = try Data(contentsOf: jsonFileURL!, options: .alwaysMapped)
let json = JSON(data: data)
// ************************************************
// NEED HELP START
// NOW WHAT???? What is the SwiftyJSON code
?????????????????????????
// NEED HELP END
// ************************************************
} catch let error {
print(error.localizedDescription)
}
}
}
func saveUploadedFilesSet() {
let documentsURL = URL(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let appURL = documentsURL?.appendingPathComponent(APP_DISTINGUISHED_NAME)
let jsonFileURL = appURL?.appendingPathComponent(UPLOADED_ITEMS_DB_JSON)
do {
let dirExists = FileManager.default.fileExists(atPath: (appURL?.absoluteString)!)
if !dirExists {
try FileManager.default.createDirectory(atPath: (appURL?.absoluteString)!, withIntermediateDirectories: false, attributes: nil)
}
// ************************************************
// NEED HELP START
// NOW WHAT???? What is the SwiftyJSON code
?????????????????????????
// NEED HELP END
// ************************************************
// Write to file code - haven't written it yet but that should be easy
} catch let error as NSError {
print(error.localizedDescription);
}
}
Any direction would be greatly appreciated. Thanks!
EDIT
I was able to figure out how to load the supplied JSON structure from file. Here is the code:
func getUploadedFileSet() {
let documentsURL = URL(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let appURL = documentsURL?.appendingPathComponent(APP_DISTINGUISHED_NAME)
let jsonFileURL = appURL?.appendingPathComponent(UPLOADED_ITEMS_DB_JSON)
if FileManager.default.fileExists(atPath: (jsonFileURL?.absoluteString)!) {
do {
let data = try Data(contentsOf: jsonFileURL!, options: .alwaysMapped)
let json = JSON(data: data)
if json != nil {
for (key, subJson) in json[kCollaborations] {
let stringArray:[String] = subJson.arrayValue.map { $0.string! }
let stringSet = Set(stringArray)
collaborationDictionary.updateValue(stringSet, forKey: key)
}
} else {
print("Could not get json from file, make sure that file contains valid json.")
}
} catch let error {
print(error.localizedDescription)
}
}
I still haven't figured out how to save the collaborationDictionary object to file. My biggest problem is figuring out how to put in the "Collaborations" key. Any ideas?
I finally got this to work. The biggest problem was that I couldn't convert collaborationDictionary to JSON. I finally had to convert it to a dictionary of arrays vs dictionary of sets. Here are the 2 methods:
// **************************************************************************
func getUploadedFileSet() {
let documentsURL = URL(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let appURL = documentsURL?.appendingPathComponent(APP_DISTINGUISHED_NAME)
let jsonFileURL = appURL?.appendingPathComponent(UPLOADED_ITEMS_DB_JSON)
if FileManager.default.fileExists(atPath: (jsonFileURL?.absoluteString)!) {
do {
let data = try Data(contentsOf: jsonFileURL!, options: .alwaysMapped)
let json = JSON(data: data)
if json != nil {
for (key, subJson) in json[kCollaborations] {
let stringArray:[String] = subJson.arrayValue.map { $0.string! }
let stringSet = Set(stringArray)
collaborationDictionary.updateValue(stringSet, forKey: key)
}
} else {
print("Could not get json from file, make sure that file contains valid json.")
}
} catch let error {
print(error.localizedDescription)
}
}
}
// **************************************************************************
func saveUploadedFilesSet() {
let documentsURL = URL(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let appURL = documentsURL?.appendingPathComponent(APP_DISTINGUISHED_NAME)
let jsonFileURL = appURL?.appendingPathComponent(UPLOADED_ITEMS_DB_JSON)
let adjustedJSONFileURL = URL(fileURLWithPath:(jsonFileURL?.absoluteString)!)
do {
let dirExists = FileManager.default.fileExists(atPath: (appURL?.absoluteString)!)
if !dirExists {
try FileManager.default.createDirectory(atPath: (appURL?.absoluteString)!, withIntermediateDirectories: false, attributes: nil)
}
// Convert set elements to arrays
var convertedCollaborationDictionary: [String:[String]] = [:]
for (sessionID, fileNameSet) in collaborationDictionary {
let array = Array(fileNameSet)
convertedCollaborationDictionary.updateValue(array, forKey: sessionID)
}
let json: JSON = JSON(convertedCollaborationDictionary)
let fullJSON: JSON = [kCollaborations:json.object]
let data = try fullJSON.rawData()
try data.write(to: adjustedJSONFileURL, options: .atomic)
} catch let error as NSError {
print(error.localizedDescription);
}
}
If you dig into the source, SwiftyJSON wraps JSONSerialization, which can both be initialized and converted back to Data which is knows how to read and write itself from disk:
func readJSON() -> JSON? {
guard let url = Bundle.main.url(forResource: "data", withExtension: "json"),
let data = try? Data(contentsOf: url) else {
return nil
}
return JSON(data: data)
}
func write(json: JSON, to url: URL) throws {
let data = try json.rawData()
try data.write(to: url)
}
Note that you can load your static data from anywhere including your Bundle, but you can only write to the sandbox (ie the Documents directory). You may wish to copy from your Bundle to the documents directory on first run if you are planning on reading/writing to the same file.
Also your sample JSON is bad (lint it). You need a comma after "Photo_0.jpeg"]

Resources