How to write to local Application Support directory on OS X - swift

I'm writing a Swift app for OS X whose primary purpose is to read data from a usb device plugged into the computer and upload it to our services layer for analyzation and storage. The app is meant to be usable by any user that has an account on the Mac it is installed on.
For support and further analytical purposes, the app is also required to include its install id, a UUID generated during the first launch of the application, in every upload. This allows our support team to associate an installation instance of our app with the set of users who have access to it so that troubleshooting and data collection is more accurate and precise.
In my app, I'm storing the install id in a file and trying to store that file in a central location, the local Application Support directory.
More specifically, I would like to store it at the following location:
Macintosh HD/Library/Application Support/MyApp/installId/installId.txt.
This is how I try save files in the Application Support directory:
var installId = String()
let fileManager = FileManager.default
var isDir: ObjCBool = false
if let appSupportDirectory = fileManager.urls(for: .applicationSupportDirectory, in: .localDomainMask).first {
let installIdDirectory = appSupportDirectory.appendingPathComponent(Bundle.main.bundleIdentifier ?? "MyApp").appendingPathComponent("installId")
let installIdFile = installIdDirectory.appendingPathComponent("installId.txt")
do {
if fileManager.fileExists(atPath: installIdFile.path, isDirectory: &isDir) {
if !isDir.boolValue {
let data = try String.init(contentsOf: installIdFile)
installId = String(data.split(separator: ":")[1])installId))")
}
else {
print("\nError: installId file appears to be a directory.")
}
}
else {
try fileManager.createDirectory(at: installIdDirectory, withIntermediateDirectories: true, attributes: nil)
let pendingInstallId = "installId:\(UUID())"
try pendingInstallId.write(to: installIdFile, atomically: false, encoding: String.Encoding.utf8)
installId = pendingInstallId
}
} catch {
print("\nError: \(error.localizedDescription)")
}
}
else {
print("\nError: Could not find Application Support Directory.")
}
When I run my app, I receive the following error:
You don't have permission to save the file "installId" in the folder "MyApp".
The error does not occur; however, if I choose to store my file in the Application Support directory in the user domain mask. The file containing the install id is created and stored in a folder called MyApp within my user Application Support directory.
I've tried searching for a solution to my problem, but it has not been too fruitful. Some posts claim that the directory I'm trying to store my file in is reserved for apps with admin privileges (source 1) while others claim i should instead be using the Application Support directory in the user domain mask for such tasks (source 2). However, I need this file to be accessible to any user who has an account on the Mac that the app is installed on, so the local domain masks' Application Support directory seems to be a better fit for this scenario.
Could someone help me out or point me in the right direction? How can I save data to this directory? If I can't feasibly do so, is there another central location that I can do it where a user is unlikely to venture into and delete that data?
Thanks in advance!

The directory /Library/Application Support/ belongs to root. You can see that in a Terminal by typing:
$ ls -al /Library | grep Appl*
drwxr-xr-x 15 root admin 480 Jan 4 16:10 Application Support
To write to that directory your App needs root privileges. Refer to Apple Documentation to securely implement this. The Apple documentation mentions authopen which seems reasonable to create a file in the support folder at the first run of your App.

Related

Hosting a web server in a local enviroment using only Flutter

Is it possible to host a Flutter web app on a local environment using a Flutter desktop-based app?
The google-search for a solution like this can be difficult, since it involves many keywords that lead to similar situations (online hosting when you need a local solution, command-line only solution, and so on).
After some digging, I ended up using the shelf package to deploy my own Flutter web app on a local network. I developed this for Windows only, so I can't guarantee it will work on other platforms.
First thing to do is obviously adding the shelf package in your pubspec.yaml: after that, this is how my main method looks like
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf_router/shelf_router.dart' as shelf_router;
[...]
void main() async{
[...]
var secureContext = SecurityContext();
try {
//privKey and cert are the String names of the two files for the SSL connection,
//placed in the root directory of the flutter project or along with the .exe file (when released)
secureContext.usePrivateKey(privKey);
secureContext.useCertificateChain(cert);
} catch (error) {
logger.e("Error on init SecurityContext");
}
try {
//this is the handler that deploys the files contained in 'webAppFolder': I just simply pasted the result of
//the flutter webapp building inside (the index.html file is the default one for flutter web)
//and put the folder in the root of the flutter project (or, again, in the same folder with the .exe file when released)
final _staticHandler = createStaticHandler("webAppFolder", defaultDocument: 'index.html');
//this I kept just for a reminder on how to deploy a static page, if needed
final _router = shelf_router.Router()
..get(
'/time',
(request) => shelf.Response.ok(DateTime.now().toUtc().toIso8601String()),
);
final cascade = shelf.Cascade()
.add(_staticHandler)
.add(_router);
try {
var server = await shelf_io.serve(
cascade.handler,
InternetAddress.anyIPv4,
mainPort, //this is the number of the port on which the webapp is deployed (I load this from a .ini file beforehand
securityContext: secureContext,
);
// Enable content compression
server.autoCompress = true;
logger.i("Serving at https://${server.address.host}:${server.port}");
} catch (err) {
logger.e("Error while serving");
logger.e(err.toString());
}
} catch (err) {
logger.e("Error while creating handler");
logger.e(err.toString());
}
runApp(MaterialApp(
[...]
This is the part related to the deploy of a web app: since the flutter desktop app already provides a GUI, I used that to add some maintenance and testing utilities to check if everything is working fine.
For more details regarding shelf, refer to their API on their pub.dev page.

Unable to request authorization for local notifications on macOS

When requesting authorization for local notifications using:
do {
_ = try await current().requestAuthorization(options: [.alert])
} catch let error {
print(error)
}
I always get the error message:
Error Domain=UNErrorDomain Code=1 "Notifications are not allowed for this application" UserInfo={NSLocalizedDescription=Notifications are not allowed for this application}
If I go to
System preferences > Notifications & Focus > My app name
I can see that they are disabled, but I never disabled them myself and I can't find a way to reset the setting.
Using Swift, macOS 12 Monterey, and the updated UserNotifications API using async/await.
Need to manually remove all traces of your app and try again from scratch, following all the steps listed here:
Uninstall the app if it is installed (delete it from Applications/)
Clean build on the Xcode project ⌘ ⇧ k
Remove any derived data [path to Xcode]/Xcode/DerivedData
Remove any Xcode products [path to Xcode]/Xcode/Products
Remove any archives [path to Xcode]/Xcode/Archives
Remove your app's containers ~/Library/Containers/[my app name]
Empty the trash bin
Do a search on finder to make sure there is no trace at all of your app anymore, search on your hard disk by the term: [my app name].app
Make sure it doesn't appear anymore on: System preferences > Notifications & Focus > [My app name]
Restart your computer
After all these steps requesting authorization should work:
do {
_ = try await current().requestAuthorization(options: [.alert])
} catch let error {
print(error)
}
It only works once
It will only work the first time, if you don't answer the notification or if you don't allow them you will need to repeat all the steps to try once more.

Deleted a file, now can't create a file of the same name

I created a file with the following Node.js code:
const {Storage} = require('#google-cloud/storage')
var gcs = new Storage()
var bucket = gcs.bucket('bucket-name')
const file = bucket.file('filename')
// fileData is a utf8 buffer
file.save(fileData, function(err) {
console.log('Error:' + err)
})
Then, I went in through the Cloud Console and deleted the file.
I then ran the code above again, but received the error "[service account] does not have storage.objects.delete access to bucket-name/filename." So I went in and added storage.objects.delete access to the service account through IAM, but I continue to get the error.
It seems that the object is still sitting inside the bucket, and it still has the old service account access (without storage.objects.delete), but I don't see the object anywhere. Versioning is suspended on this bucket.
I have since gone through the same steps with the same bucket but using a different filename and don't see the error message. This seems to show that the new service account access is being properly applied to new files, but not to old files. This is surprising, since I'm using "Bucket Policy Only" on this bucket.
Can anyone figure out how to fix this? Thanks!
Cloud Storage Object metadata
1. await bucket
.file(filePath)
.delete({ ignoreNotFound: true });
// Deleting file with a name.
const blob = bucket.file(filePath);
2. await blob.save(fil?.buffer);
//Saving File with the same name
3. const [metadata] = await storage
.bucket(bucketName)
.file(filePath)
.getMetadata();
newDocObj.location = metadata.mediaLink;
I have used metadata.mediaLink to get the latest download link
of the uploaded file from Google Bucket Storage.

Failed to load launch URL with error: Error Domain=TVMLKitErrorDomain Code=3 "(null)"

Description:
I created a new TVML project and launched it. The first error was the App Transport Security, which I fixed via Info.plist :
App Transport Security Settings -> Allow Arbitrary Loads -> YES
Then I ran it again and I'm getting this error:
Failed to load launch URL with error: (null)
appController(_:didFailWithError:) invoked with error: Error
Domain=TVMLKitErrorDomain Code=3 "(null)"
The project seems to stop here (application func in AppDelegate.swift):
appControllerContext.launchOptions["BASEURL"] = AppDelegate.tvBaseURL
print(launchOptions) //returns nil
//error on following line
if let launchOptions = launchOptions as? [String: AnyObject] {
//does not enter here
for (kind, value) in launchOptions {
appControllerContext.launchOptions[kind] = value
}
}
What I've tried:
I attempted changing the tvBaseURL from "http://localhost:9001/" to http://MY-IP-ADDRESS-HERE:9001/
but that didn't change anything.
Question:
What is causing this error and how do I solve it?
You should start the server with port number
enter the following command in terminal
ruby -run -ehttpd . -p9001
And finally your tvBaseURL should navigate to the server folder like this
"http://yourLocalhost:9001/Downloads/TVMLCatalogUsingTVMLTemplates/Server/"
I also faced the same problem, I solved it by changing tvBaseURL in AppDelegate
static let tvBaseURL = "http://127.0.0.1:9001/Downloads/TVMLCatalogUsingTVMLTemplates/Server/"
As you see - I have to show exact path to Server folder. That also works if you put it to some web server.
Hope that it can help!
I just ran into this issue. You need to pay close attention to the terminal output.
I got:
[2019-03-15 12:28:43] INFO WEBrick 1.3.1
[2019-03-15 12:28:43] INFO ruby 2.3.7 (2018-03-28) [universal.x86_64-darwin17]
/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/socket.rb:205:
in `bind': Address already in use - bind(2) for 0.0.0.0:9001 (Errno::EADDRINUSE)
Address already in use - bind(2) for 0.0.0.0:9001
At this point you either have to choose a different port number (if you decide to do such then make sure your server's port and your Xcode's project port match) or kill the previous server by ctrl + c or just killing that terminal window.
Also note in some of Apple's sample projects the ruby -run -ehttpd . -p9001 command is to be done in a folder named Server and for others it's just suppose to be done in the App's main folder. Just look into the README file to figure this out.

Static resource reload with akka-http

In short: is it possible to reload static resources using akka-http?
A bit more:
I have Scala project.
I'm using App object to launch my Main
class.
I'm using getFromResourceDirectory to locate my resource
folder.
What I would like to have is to hot-swap my static resources during development.
For example, I have index.html or application.js, which I change and I want to see changes after I refresh my browser without restarting my server. What is the best practise of doing such thing?
I know that Play! allows that, but don't want to base my project on Play! only because of that.
Two options:
Easiest: use the getFromDirectory directive instead when running locally and point it to the path where your files you want to 'hotload' are, it serves them directly from the file system, so every time you change a file and load it through Akka HTTP it will be the latest version.
getFromResourceDirectory loads files from the classpath, the resources are available because SBT copies them into the class directory under target every time you build (copyResources). You could configure sbt using unmanagedClasspath to make it include the static resource directory in the classpath. If you want to package the resources in the artifact when running package however this would require some more sbt-trixery (if you just put src/resources in unmanagedClasspath it will depend on classpath ordering if the copied ones or the modified ones are used).
I couldn't get it to work by adding to unmanagedClasspath so I instead used getFromDirectory. You can use getFromDirectory as a fallback if getFromResourceDirectory fails like this.
val route =
pathSingleSlash {
getFromResource("static/index.html") ~
getFromFile("../website/static/index.html")
} ~
getFromResourceDirectory("static") ~
getFromDirectory("../website/static")
First it tries to look up the file in the static resource directory and if that fails, then checks if ../website/static has the file.
The below code try to find the file in the directory "staticContentDir". If the file is found, it is sent it back to the client. If it is not found, it tries by fetching the file from the directory "site" in the classpath.
The user url is: http://server:port/site/path/to/file.ext
/site/ comes from "staticPath"
val staticContentDir = calculateStaticPath()
val staticPath = "site"
val routes = pathPrefix(staticPath) {
entity(as[HttpRequest]) { requestData =>
val fullPath = requestData.uri.path
encodeResponse {
if (Files.exists(staticContentDir.resolve(fullPath.toString().replaceFirst(s"/$staticPath/", "")))) {
getFromBrowseableDirectory(staticContentDir.toString)
} else {
getFromResourceDirectory("site")
}
}
}
}
I hope it is clear.