How to get drive letter returned as a string? - windows

I'm struggling to get a string of the prefix of a path. All I want to do is get the "D:" from the string "D:\Household\Internet\September_2022_Statement.pdf".
If I follow the example for std:path:Component in here, I can see the value I want but don't know how to get to it.
The code I am running is:
let filepath = "D:\\Household\\Internet\\September_2022_Statement.pdf";
let path = Path::new(filepath);
let components = path.components().collect::<Vec<_>>();
for value in &components {
println!("{:?}", value);
}
The output I get is:
Prefix(PrefixComponent { raw: "D:", parsed: Disk(68) })
RootDir
Normal("Household")
Normal("Internet")
Normal("September_2022_Statement.pdf")
How do I get the raw value "D:" from Prefix(PrefixComponent { raw: "D:", parsed: Disk(68) })?

Looks like components is an iterator of instances of the Component enum, which is declared as
pub enum Component<'a> {
Prefix(PrefixComponent<'a>),
RootDir,
CurDir,
ParentDir,
Normal(&'a OsStr),
}
Since you know that the drive is a Prefix, you can test for that.
let filepath = "D:\\Household\\Internet\\September_2022_Statement.pdf";
let path = Path::new(filepath);
let components = path.components().collect::<Vec<_>>();
for value in &components {
if let std::path::Component::Prefix(prefixComponent) = value {
return Some(value.as_os_str());
// instead of returning you could set this to a mutable variable
// or you could just check the first element of `components`
}
}
None // if this were a function that returned Option<String>
The example from the Rust docs is
use std::path::{Component, Path, Prefix};
use std::ffi::OsStr;
let path = Path::new(r"c:\you\later\");
match path.components().next().unwrap() {
Component::Prefix(prefix_component) => {
assert_eq!(Prefix::Disk(b'C'), prefix_component.kind());
assert_eq!(OsStr::new("c:"), prefix_component.as_os_str());
}
_ => unreachable!(),
}

Thanks to #Samathingamajig and #PitaJ, I was able to achieve what I needed. Obviously, I am a Rust newbie so I'm sure there is a cleaner way of doing this, but combining the help suggested, this works for me:
let filepath = "D:\\Household\\Internet\\September_2022_Statement.pdf";
let path = Path::new(fileName);
let components = path.components().collect::<Vec<_>>();
let mut os_string = OsString::new();
match path.components().next().unwrap() {
Component::Prefix(prefix_component) => {
os_string = prefix_component.as_os_str().into();
}
_ => todo!()
}
println!("{}", os_string.to_str().unwrap());
Resulting output:
D:

Related

Initializer for conditional binding must have Optional type, not '[String : Double]'

I already read all the answers in stack overflow for similar problems, and tried the suggestions for them, but cannot solve this error (Initializer for conditional binding must have Optional type, not '[String:Double]'), which occurs on line "if let jsonDictionary = json {". Please help! Thanks in advance!
func getPrice(cprCcy: String, ccy: String){
if let url = URL(string: "https://min-api.cryptocompare.com/data/price?fsym=" + cprCcy + "&tsyms=" + ccy){
URLSession.shared.dataTask(with: url) {(data, response, error) in
if let data = data {
print ("connected to the url")
if let json = try? JSONSerialization.jsonObject(with: data, options:[]) as? [String:Double]{
if let jsonDictionary = json {
if let price = jsonDictionary[ccy] {
print(price)
}
}
}
}
else{
print("wrong =(")
}
}.resume()
}
}
In the end, I found a simple solution:
replaced this part :
if let jsonDictionary = json {
if let price = jsonDictionary[ccy] {
print(price)
}
}
by this:
if let price = json[ccy] {
print(price)
}
that is, just eliminated the line that was causing me problems and moved the variable to the next line.

Getting alias path of file in swift

I'm having trouble resolving the alias link on mac. I'm checking if the file is an alias and then I would want to receive the original path. Instead I'm only getting a File-Id.
Anly ideas?
func isFinderAlias(path:String) -> Bool? {
var isAlias:Bool? = false // Initialize result var.
// Create a CFURL instance for the given filesystem path.
// This should never fail, because the existence isn't verified at this point.
// Note: No need to call CFRelease(fUrl) later, because Swift auto-memory-manages CoreFoundation objects.
print("path before \(path)");
let fUrl = CFURLCreateWithFileSystemPath(nil, path, CFURLPathStyle.CFURLPOSIXPathStyle, false)
print("path furl \(fUrl)");
// Allocate void pointer - no need for initialization,
// it will be assigned to by CFURLCopyResourcePropertyForKey() below.
let ptrPropVal = UnsafeMutablePointer<Void>.alloc(1)
// Call the CoreFoundation function that copies the desired information as
// a CFBoolean to newly allocated memory that prt will point to on return.
if CFURLCopyResourcePropertyForKey(fUrl, kCFURLIsAliasFileKey, ptrPropVal, nil) {
// Extract the Bool value from the memory allocated.
isAlias = UnsafePointer<CFBoolean>(ptrPropVal).memory as Bool
// it will be assigned to by CFURLCopyResourcePropertyForKey() below.
let ptrDarwin = UnsafeMutablePointer<DarwinBoolean>.alloc(1)
if ((isAlias) == true){
if let bookmark = CFURLCreateBookmarkDataFromFile(kCFAllocatorDefault, fUrl, nil){
let url = CFURLCreateByResolvingBookmarkData(kCFAllocatorDefault, bookmark.takeRetainedValue(), CFURLBookmarkResolutionOptions.CFBookmarkResolutionWithoutMountingMask, nil, nil, ptrDarwin, nil)
print("getting the path \(url)")
}
}
// Since the CF*() call contains the word "Copy", WE are responsible
// for destroying (freeing) the memory.
ptrDarwin.destroy()
ptrDarwin.dealloc(1)
ptrPropVal.destroy()
}
// Deallocate the pointer
ptrPropVal.dealloc(1)
return isAlias
}
EDIT:
Both Answers are correct!
I would choose the answer of mklement0 due to the originally not stated requirement that the code run on 10.9 which makes it more flexible
This is a solution using NSURL.
It expects an NSURL object as parameter and returns either the original path if the url is an alias or nil.
func resolveFinderAlias(url:NSURL) -> String? {
var isAlias : AnyObject?
do {
try url.getResourceValue(&isAlias, forKey: NSURLIsAliasFileKey)
if isAlias as! Bool {
do {
let original = try NSURL(byResolvingAliasFileAtURL: url, options: NSURLBookmarkResolutionOptions())
return original.path!
} catch let error as NSError {
print(error)
}
}
} catch _ {}
return nil
}
Swift 3:
func resolveFinderAlias(at url: URL) -> String? {
do {
let resourceValues = try url.resourceValues(forKeys: [.isAliasFileKey])
if resourceValues.isAliasFile! {
let original = try URL(resolvingAliasFileAt: url)
return original.path
}
} catch {
print(error)
}
return nil
}
Be aware to provide appropriate entitlements if the function is called in a sandboxed environment.
vadian's answer works great on OS X 10.10+.
Here's an implementation that also works on OS X 10.9:
// OSX 10.9+
// Resolves a Finder alias to its full target path.
// If the given path is not a Finder alias, its *own* full path is returned.
// If the input path doesn't exist or any other error occurs, nil is returned.
func resolveFinderAlias(path: String) -> String? {
let fUrl = NSURL(fileURLWithPath: path)
var targetPath:String? = nil
if (fUrl.fileReferenceURL() != nil) { // item exists
do {
// Get information about the file alias.
// If the file is not an alias files, an exception is thrown
// and execution continues in the catch clause.
let data = try NSURL.bookmarkDataWithContentsOfURL(fUrl)
// NSURLPathKey contains the target path.
let rv = NSURL.resourceValuesForKeys([ NSURLPathKey ], fromBookmarkData: data)
targetPath = rv![NSURLPathKey] as! String?
} catch {
// We know that the input path exists, but treating it as an alias
// file failed, so we assume it's not an alias file and return its
// *own* full path.
targetPath = fUrl.path
}
}
return targetPath
}
Note:
Unlike vadian's solution, this will return a value even for non-alias files, namely that file's own full path, and takes a path string rather than a NSURL instance as input.
vadian's solution requires appropriate entitlements in order to use the function in a sandboxed application/environment. It seems that this one at least doesn't need that to the same extent, as it will run in an Xcode Playground, unlike vadian's solution. If someone can shed light on this, please help.
Either solution, however, does run in a shell script with shebang line #!/usr/bin/env swift.
If you want to explicitly test whether a given path is a Finder alias, see this answer, which is derived from vadian's, but due to its narrower focus also runs on 10.9.
Here's a Swift 3 implementation, based largely on vadian's approach. My idea is to return a file URL, so I effectively combine it with fileURLWithPath. It's an NSURL class extension because I need to be able to call into it from existing Objective-C code:
extension NSURL {
class func fileURL(path:String, resolveAlias yn:Bool) -> URL {
let url = URL(fileURLWithPath: path)
if !yn {
return url
}
do {
let vals = try url.resourceValues(forKeys: [.isAliasFileKey])
if let isAlias = vals.isAliasFile {
if isAlias {
let original = try URL(resolvingAliasFileAt: url)
return original
}
}
} catch {
return url // give up
}
return url // really give up
}
}
URL variant I need to return nil (not an alias or error) else original - Swift4
func resolvedFinderAlias() -> URL? {
if (self.fileReferenceURL() != nil) { // item exists
do {
// Get information about the file alias.
// If the file is not an alias files, an exception is thrown
// and execution continues in the catch clause.
let data = try NSURL.bookmarkData(withContentsOf: self as URL)
// NSURLPathKey contains the target path.
let rv = NSURL.resourceValues(forKeys: [ URLResourceKey.pathKey ], fromBookmarkData: data)
var urlString = rv![URLResourceKey.pathKey] as! String
if !urlString.hasPrefix("file://") {
urlString = "file://" + urlString
}
return URL.init(string: urlString)
} catch {
// We know that the input path exists, but treating it as an alias
// file failed, so we assume it's not an alias file so return nil.
return nil
}
}
return nil
}

Is there a better way of coping with Swift's nested "if let" "pyramid of doom?"

Is there a better way of dealing with a chain of optional properties than nested if let statements? I have been advised to use if lets when examining optional properties, which makes sense as it deals with them at compile time rather than run time, but it looks like utter madness! Is there is a better way?
Here is the current "pyramid of doom" I have ended up with, as an example:
( users: [ JSONValue ]? ) in
if let jsonValue: JSONValue = users?[ 0 ]
{
if let json: Dictionary< String, JSONValue > = jsonValue.object
{
if let userIDValue: JSONValue = json[ "id" ]
{
let userID: String = String( Int( userIDValue.double! ) )
println( userID )
}
}
}
Post-script
Airspeed Velocity's answer below is the right answer, but you will need Swift 1.2 to use multiple lets separated by commas as he suggests, which only currently runs in XCode 6.3, which is in beta.
As commenters have said, Swift 1.2 now has multiple-let syntax:
if let jsonValue = users?.first,
json = jsonValue.object,
userIDValue = json[ "id" ],
doubleID = userIDValue.double,
userID = doubleID.map({ String(Int(doubleID))})
{
println( userID )
}
That said, in this instance it looks like you could might be able to do it all via optional chaining in 1.1, depending on what your objects are:
if let userID = users?.first?.object?["id"]?.double.map({String(Int($0))}) {
println(userID)
}
Note, much better to use first (if this is an array) rather than [0], to account for the possibility the array is empty. And map on the double rather than ! (which would blow up if the value is not double-able).
UPDATE for Swift-3 : The syntax has changed :
if let jsonValue = users?.first,
let json = jsonValue.object,
let userIDValue = json[ "id" ],
let doubleID = userIDValue.double,
let userID = doubleID.map({ String(Int(doubleID))})
{
println( userID )
}
In Swift 2, we have the guard statement.
Instead of:
func myFunc(myOptional: Type?) {
if let object = myOptional! {
...
}
}
You can do it like this:
func myFunc(myOptional: Type?) {
guard array.first else { return }
}
Check http://nshipster.com/guard-and-defer/ from NSHipster.

Converting JSON from AlamoFire/SwiftyJSON to Dictionary in Swift/Xcode

My head is going to explode :) - I've been trying to get a JSON String from my server to a Dictionary Value, and I can't get it to work.
I'm trying to get (from my Server - this is dynamic and I want my app to be able to pull new data from the server when needed):
{"1":"Location 1","2":"Location 2","3":"Location 3"}
To this Dictionary in Xcode using Swift:
var labels = [
1 : "Location 1",
2 : "Location 2",
3 : "Location 3"
]
This has got to be pretty straight forward, but for the life of me I can't figure it out...
Here's my Swift - I can get it to pull the information from the server, but I can't get it into a dictionary like I need
var postEndpoint: String = "http://www.myserver.net/app/campus.php"
Alamofire.request(.GET, postEndpoint)
.responseJSON { (request, response, data, error) in
if let anError = error
{
println("error")
println(error)
}
else if let data: AnyObject = data
{
let post = JSON(data)
println(post)
}
}
which results in:
{
"1" : "Location 1",
"2" : "Location 2",
"3" : "Location 3"
}
The End Result that I'm using this for is an iBeacon implementation with the following code:
let knownBeacons = beacons.filter{ $0.proximity != CLProximity.Unknown }
if (knownBeacons.count > 0) {
let closestBeacon = knownBeacons[0] as CLBeacon
let locationID = post[closestBeacon.minor.integerValue]
self.locationLabel.text = locationID
self.view.backgroundColor = self.colors[closestBeacon.minor.integerValue]
}
The error I'm getting is at self.locationLabel.text = locationID 'JSON' is not convertible to 'String', I do not get this error when I use the static var labels dictionary. Am I trying to get the data from the server incorrectly? What am I doing wrong??? I think the var labels having an undeclared Type allows Swift to figure out what it needs to, how do I do the same from the JSON part?
Oh you were so close!
Your problem is that your post dictionary is a [String: String] and not an [Int: String] like you think it is. You have a few ways to fix it, but the easiest for now would be to just do the following:
let locationID = post[String(closestBeacon.minor.integerValue)]!
While this will certainly work, a better solution would be to convert your post into a [Int: String] typed dictionary like you expect in the responseJSON closure. Here's how this could work.
let json = JSON(data)
var post = [Int: String]()
for (key, object) in json {
post[key.toInt()!] = object.stringValue
}
You would want to add some safety around what to do if the key or object were not able to be converted to an Int or String respectively, but I'll leave that to you.
If having a [String: String] is sufficient for anyone, he/she could try the following code:
let responseString = "{\"1\":\"Location 1\",\"2\":\"Location 2\",\"3\":\"Location 3\"}"
if let dataFromString = responseString.data(using: String.Encoding.utf8, allowLossyConversion: false) {
let json = JSON(data: dataFromString)
var labels = json.dictionaryObject! as! [String: String]
print(labels)
}
The result is:
["2": "Location 2", "1": "Location 1", "3": "Location 3"].
I solved this by working in reverse. I changed the call. Instead of getting the Dictionary of Values from the Server, I just query the Server with the Single Variable that I already had from the variable
closestBeacon.minor.integerValue
And then get the string that I needed from the server and that solved my problem. Still has the same number of calls to the server, so no additional overhead was added. Sometimes you just have to think outside the box that you put yourself into.
If anybody can solve this the other direction, I'm still eager to hear how it could work.
You need to check recursively for inner JSON as well
Use below function to convert into Dictionary or Array
static func toDictionary(from: JSON) -> Any? {
if from.type == .array {
var array = [Any]()
for _i in 0..<from.count {
array.append(toDictionary(from: from[_i]) as Any)
}
return array
} else if from.type == .dictionary {
var dictionary = [String: Any]()
for (key, value) in from {
dictionary[key] = toDictionary(from: value)
}
return dictionary
} else {
return from.stringValue
}
}

Retrieve ALAsset or PHAsset from file URL

Selecting images in Photos.app to pass to an action extension seems to yield paths to images on disk (e.g.: file:///var/mobile/Media/DCIM/109APPLE/IMG_9417.JPG). Is there a way to get the corresponding ALAsset or PHAsset?
The URL looks like it corresponds to the PHImageFileURLKey entry you get from calling PHImageManager.requestImageDataForAsset. I'd hate to have to iterate through all PHAssets to find it.
I did what I didn't want to do and threw this dumb search approach together. It works, although it's horrible, slow and gives me memory issues when the photo library is large.
As a noob to both Cocoa and Swift I'd appreciate refinement tips. Thanks!
func PHAssetForFileURL(url: NSURL) -> PHAsset? {
var imageRequestOptions = PHImageRequestOptions()
imageRequestOptions.version = .Current
imageRequestOptions.deliveryMode = .FastFormat
imageRequestOptions.resizeMode = .Fast
imageRequestOptions.synchronous = true
let fetchResult = PHAsset.fetchAssetsWithOptions(nil)
for var index = 0; index < fetchResult.count; index++ {
if let asset = fetchResult[index] as? PHAsset {
var found = false
PHImageManager.defaultManager().requestImageDataForAsset(asset,
options: imageRequestOptions) { (_, _, _, info) in
if let urlkey = info["PHImageFileURLKey"] as? NSURL {
if urlkey.absoluteString! == url.absoluteString! {
found = true
}
}
}
if (found) {
return asset
}
}
}
return nil
}
So this is commentary on ("refinement tips") to your auto-answer. SO comments don't cut it for code samples, so here we go.
You can replace your for-index loop with a simpler for-each loop. E.g. something like:
for asset in PHAsset.fetchAssetsWithOptions(nil)
As of the last time I checked, the key in info["PHImageFileURLKey"] is undocumented. Be apprised. I don't think it will get you rejected, but the behavior could change at any time.

Resources