Well Isn't That Just Dandy

Yes, yes it is

So for some of you following me on Instagram/Facebook/Twitter, you’ve noticed the tie/vest pics i’ve been posting. Honestly, it started as a lark, but folks seem to dig it, so cool.

If you’re wondering where I get stuff, to date, that’s been easy: The Men’s Wearhouse Clearance rack. Seriously. I get really good, quality shirts/vests/ties for cheap. Cheaper than Target, and much better quality. Like shirts and/or vests for $12. Pants, meh, I get those at Costco.

The tie knots are tutorials from a couple places. https://agreeordie.com/features/fashion/616-how-to-tie-a-necktie-eldredge-knot/ is a good start. They have videos and diagrams. For a lot of the others, it’s videos or nothing, and honestly, some of them, even a good diagram doesn’t work that well. There’s one guy who’s my go to on these, Patrick Novotny. He’s engaging, and he does a really solid job of showing you how to tie some of these really complex knots in a way that doesn’t leave you falling behind or floundering.

I’ve found some good potentials for vests, so as I add to the collection, I’ll show them off. But as a start, Men’s Wearhouse is solid.

It Is Not Your Place

At some point, men have to stop being jerks

First, read this twitter thread, and its associated sub-threads. It’s not a pleasant read, and if you feel called-out by it, good, you should. I genuinely hope you are feeling a good amount of shame and guilt, as the behavior it talks about is bad, and anyone engaging in it should feel bad. The idea, the mental breakdown that passes for a coherent thought that a random guy has the right to go up to a stranger and “pop quiz” her on self-defense, to “educate” her on how she’s being unsafe is…there are not enough letters in “appalling” to properly get across how appalling this is.

First, it’s not your damned place to do that. It never is. And those of us not engaging in this awful behavior, regardless of where we identify on the gender spectrum wish you would stop. Yesterday. Just…just what kind of egotistical, self-centered, full frontal idiocy has taken residence in your brain to where you think it is your job to require a random woman to perform for you? I want it to be brain worms, or an undiagnosed tumor, because that way, it’s not you actually fully doing it.

It is never brain worms or an undiagnosed tumor. It is however, as neat an example of male privilege and patriarchal behavior as one could wish for. The assumption that any woman exists for you to “educate” on some random subject. I mean, there is an unintended benefit. Everyone witnessing this will indeed be aware of how dangerous men are. Starting with you. Because what you are doing is attacking a random woman.

If you wish to raise “but I meant well”, save your breath, and your effort, your intent is meaningless. In fact, it makes things worse, because you actually think, according to your intent, your stated intent, that you are behaving not as an attacker, as someone to treat with extreme wariness and a healthy amount of fear, but as a “friend”, as someone doing someone else a favor. You are literally so…stupid, so self-centeredly, preciously, stupid that you think you are behaving in a societally beneficial way.

You are not. In fact, the only difference between you and a rapist is the lack of sexual assault. You have however, still committed several crimes, of this there is neither doubt nor argument. When you come at someone, when you touch them or verbally engage with them in a threatening manner, deliberately designed to create a fight-or flight reflex, you are committing crimes. You are a criminal. In a just world, you would be arrested and tried as such. But given how (American) society (at least) treats such paternalistic fuckwittery, you’d instead get a pat on the back and encouragement.

“Gee, why do so many women hate men.”

You. It’s because of you, and your ilk, and I rarely use the word “ilk” with such satisfaction as I do here. You aren your ilk are why. If “asshole” could be a gender, so many people would make you its spokesperson. It is not women’s job to exist in a perpetual state of battle readiness just to satisfy you. It is your job to not be an asshole. Alas, we’ve already seen you’re a failure in that arena.

As you are so determined to be “that guy”, allow me to point out that you are also lucky beyond measure, because if one of the women you attacked were actually trained or were actually concealed carrying, you know, the things you (don’t really) want them to be so they can not be (very much be) helpless against “bad” guys (like you), then you would be at best getting a ride in an ambulance, if not a hearse. Let us look at the scenario with the woman who was locking her bike. (for background, I have more than two decades of experience with martial arts/self-defense and am a currently active practitioner. In the last year I’ve had multiple bruised ribs, at least one fracture, and a possible fracture of another bone. Or two. I am not an idle observer in this area.)

I’m kneeling down to lock up my bike on a busy corner; a man steps over the center bar of the bike and onto the shoulder strap of my bag. He then explains, while I’m immobilized, that this was a little free safety advice. No shoulders bags!

So, there are two positions that “kneeling” can apply to, I’m going to go with the most common, on one knee, with one foot on the ground. The woman has some form of shoulder bag, probably in an over the shoulder, ’round the neck, under one arm configuration, that would be the easiest way to carry a shoulder bag on a bike. (We’ll assume bicycle, given her description.) She also, given her activity, has a rather large either U-Lock or a reasonably heavy padlock/chain combination.

Dipshit comes up, probably from behind, and steps on her strap, probably right where it meets the bag, that would be the easiest way for him to pull off this bugfuckery. She’s helpless now, right? Nope.

Since she can’t easily turn, her first priority is to gain some freedom of movement. As the pressure on the shoulder strap would be pulling her down and to one side, her best bet would be to use the foot already planted and shove as hard as she can into the foot/lower leg of her attacker. She only needs to move his foot a bit to get some free space. Even against a large man, this would be effective, especially given our Dudley-Dumb-Right here.

Once she disengages his foot off that strap, then the variables increase. She can pull back the other way, creating more space between her and her attacker, possibly getting the bike or whatever between her and him, (a good choice, gives her more overall options.) If she’s really successful, and more mad than scared, then he’s stumbling as well, and she has a very heavy, very hard weapon in her hands, a solid case of self-defense, and a chucklehead about to have the worst day ever. If he’s lucky, she only hits him in the nuts with the lock. If he’s unlucky, she lays his ass out with it. Possibly permanently.

If she’s got a good grip on the chain, since she knows where at least one leg is, and a quick look will verify the location of the other, then she has a really good triangulation on where his skull is. She also has a chain, with a padlock of some kind on it, and one functioning arm. Even a near-direct hit will work for her immediate needs.

See, here’s the thing these yutzes never think about: while he knows he’s “helping”, she, dear reader, she does not. She only knows that someone is attacking her. And I know women, such women, that would turn that attacker into fuckin’ hamburger, especially if they have such a weapon as a bike lock.

But that’s the benefit of patriarchal privilege isn’t it? To be so sure, solely based on observed gender differences, that you can attack a woman and “reasonably” expect not only that she won’t hurt or kill you, but thank you. That is privilege writ large, it is the most perfect example of privilege one will ever find.

It is my fondest hope that the next time one of these preciously privileged anthropomorphized sphincters pulls something like this, that he does in fact do so on the wrong woman, and she only stops hitting him because her arms and legs are tired. Then, maybe, calls the cops. Or better yet, just goes on about her day and leaves him to the mercy of other random strangers.

I Can't Believe…

Weight Problems Never Go Away

A couple of years ago, January 2018 to be precise, I changed how I eat. My wife, Melissa, had been doing the keto thing, only a really nice implementation, i.e. not just eggs and steak. Rather, there was a lot of modifications to food I’d eat anyway, like Indian and the like, and as it was working for her, and I was heartily sick of the fifty or so extra pounds I was carrying, I decided to change how I ate, and joined her.

It seems to have worked, across the next year I went from 232lbs to 165lbs, and have done a slow sine wave around 165 going ±2 lbs ever since. I’ve heard about what you’d expect. “Good job”, “you look great”, etc. But I’ve also heard the slight digs: “you’re too skinny”, “you need to eat a sandwich”, and the rest of it. To the first group, thank you, to the second, if you think you’re being funny, only you are amused by what you are saying. I most certainly am not.

There is a third group, the diet shamers, the ones who bag on any diet you’re on. My “favorites” are “just eat fewer calories than you burn, you don’t need a diet to do that.” Well fuckleberry hound, that’s a diet too. For them though, I have a special anger. Because a) they’re full of shit, b) none of them know me well enough to know why some things work for me and others don’t, c) they’re never, ever actually working in the field, and d) none of them could walk up a flight of stairs without needing twenty minutes of rest and a vomit bucket. If you look like a Ditto, don’t tell anyone how to get in shape.

The full of shit part is easy. Google “The death of the calorie”. Probably the best article, or more correctly, the one I’ve found the most useful is here. The gist of it is, it’s not that simple. Calorie counts of food can be off by between 8%-70%. Regulation allows for a 20% inaccuracy. So basically, you don’t know how many calories something has. So much for counting calories as being the “one true way”.

Also, I hate shaming people for trying to lose weight. It’s stupid. It’s wrong. It’s cruel. And yes, I am routinely, routinely frustrated when the people giving me shit are in significantly worse shape than I am. Again, if you look like a goddamned Pokemon Ditto, or you’re not actually working as a dietician or similar, do not tell me that how I am going about my diet and overall lifestyle is wrong or bad, because clearly, you are not an expert.

But I keep hearing that shit. “Eat something, you’re too skinny.” Somehow, I bet if I said “Maybe you should stop eating something, you’re too fat.”, that would not be okay.

Because here’s the thing: weight problems don’t go away. Yeah, I lost the weight, and I’m happy about that. I genuinely am. But, it’s work. Note earlier, I didn’t say “I went on a diet.” I said “I changed how I eat.” Diets, at least in the way we normally think of them, are temporary. What I did was permanent. There’s things I don’t eat anymore, that I may never eat. There’s things, that if I do eat them, are very special occasion things.

But I never “just eat” anymore. I can’t. I used to, and I was 70lbs overweight. I can’t tell you the difference losing that has made, but it’s huge. Like every part of my life was changed by losing that weight, all of it for the better. Well, okay, as it turns out, I have a comically large skull, so that looks a little odd. Other than that, it’s been really fantastic.

But I can’t “just eat”. I am hyper-aware of everything I eat. If I eat a fucking peanut, I have weighed that against a lot of other factors. Everything that goes in my mouth only does so after a process. It sucks, but that is what works, for me at least. Every day, there is a non-zero amount of work to stay on this path. Every day, there’s some schmuck who, consciously or not, goes out of their way to make doing so harder. I’m glad I’m not also an alcoholic, because holy fuck, if I had to deal with the food shit and the “come on, one drink won’t kill you” idiots? Eeesh.

People look at me now and see someone who may be “too skinny” or is in “great shape.” I still see that bit of gut I can never lose, not without surgery or going hardcore crossfit/lifting. Like if I worked really hard, I might lose another ten pounds. I don’t think it would be worth it, but I think about it. Every day. Every day, I think about it. Every day I look down and I see all the things I’ve alway seen as a fat kid who is occasionally skinny, or more accurately, not fat. It’s hard to remember that I’m doing okay. That’s one reason I weigh myself every day, because no matter what my head tells me, as long as I see 165±2, that’s the number I need to see.

The number tells me it’s okay. Sometimes, it’s the only thing, because god knows, people won’t. People will do a lot of work to, for whatever reason, make you want to give up, to stop trying, and if you try to push back, you get some bullshit about how they’re “only being honest/factual/scientifically accurate/etc.” What they’re doing is justifying being a jerk.

Don’t do that. If someone is trying to lose weight, no matter how they are going about it, unless it’s literally dangerous, and unless you’re a dietician or other licensed professional in that area of study, you don’t know that, (I don’t care what the SciBabe says on this, you’re either working in the field, or you’re a putz with a big mouth.) Don’t make the comments. Don’t shit on someone’s process. You know what you say if someone is trying to change how they eat?

“How can I help?”

“What foods are okay on this?”

“Do you need someone to be a diet/workout buddy?”

Be supportive. Be positive. Be encouraging. If someone who never excercises is feeling good because they walked around the block that day, cheer that shit, even if you run marathons. Maybe especially then.

Because it’s hard. Babies, it’s so goddamned hard, I’ve been working on this for almost half a century and it’s so hard. Don’t make this harder than it already is.

Be like…well, here, be like Arnold.

That’s how you help people.

And if anyone going through this process needs some support, I’m @bynkii on twitter. You need someone to be like Arnold for you, I’m here for that, I am so here for that. I got your back.

New Outlook Bug List

Well, mostly NOM

Because Outlook’s in-app bug support doesn’t let you submit a new issue until the old/current issue is closed, here’s a list of things I have seen (not including the big things like no contacts/notes/tasks functionality):

  1. The only indication for unread emails in a folder is the number. There’s no bolding or other indication (NOM)
  2. If a folder has subfolders with unread emails, and the higher level folder is collapsed/closed, there’s no indication of unread emails within (NOM)
  3. .olm file export appears to be broken, as Outlook can’t read its own .olm files (NOM and old Outlook)
  4. The Scripting Dictionary has no knowledge of O365 accounts at all. (NOM and old Outlook)
  5. Can’t change description of any account using the new sync engine (NOM) fixed in 16.33.19120904 Insider fast build
  6. O365 accounts created with NOM don’t exist for old outlook if you switch back. (NOM & old Outlook)

As I find more/these get fixed, I’ll try to update this list.

On Wasted Features

Outlook Mac Templates could be so good. Yet they barely exist.

As you can guess, this is about email templates in Outlook for Mac. Specifically in NOM, but I doubt it’s much different in “Old” Outlook. As they exist right now, well, I don’t know why they exist. They don’t seem to do much of anything but make resending an email…not easier than forwarding it. I mean, as soon as the “resend” makes it back into Gmail and O365 accounts in NOM, the entire advantage of templates as they exist goes out the window.

There’s a number of reasons. First, UI for using them is the Finder. Which is not ideal for a number of reasons, but the biggest one is that because Outlook includes no QuickView option for Outlook Templates, you can’t see what one looks like before you use it:

I hope you use really descriptive names

That’s the entire UI for email templates in Outlook for Mac. The Finder. And that’s kind of well…halfassed. Which is why almost no one uses them. Forget where you saved that template? Hope you enjoy spotlight searches or locate in the command line. Or command line find. Because Outlook won’t be of any use.

Oh, and of course, any templates you create on one machine won’t be seen by another unless you’re using OneDrive or iCloud. Even if you use OneDrive, it’s not like Outlook on the Web can use templates worth beans. I mean, if you go into the options, you can enable it, but creating them is non-obvious, and I’ve no idea, nor am I inclined to spend time searching, to figure out where they are saved even if I spend the time figuring out how to create them.

I’ve no idea about Windows, I don’t use Outlook for Windows, I’m much happier that way. And of course, templates don’t seem to exist for Outlook Mobile. So, as best I can tell, templates are only really usable on two of Outlook’s platforms, barely exist on a third and don’t exist at all on two more.

The point is, templates, to be useful have to be obvious, discoverable, predictable, and shareable. Outlook’s implementation currently fails at all of these. That’s a shame because if they added things like a proper UI for them with solid previews, made them accessible for all versions of Outlook for a given O365 account, then they might be better than “resend”. They’d be very useful for orgs that have specifically formatted emails they need to send out (and no, Word is not the way around this), etc. Templates, done well, are useful things.

Which as far as I can tell, is only available on Outlook:Mac anyway.

Really, given how barely there they are, if the Outlook team wanted to clean up some unused stuff, templates would be an easy target. I doubt their absence would actually create any extra work, and were OWA and the other versions of Outlook not on the Mac implement a “resend” feature, most of the use of templates would go away even further.

I’d rather they fix it and make it useful, because there’s a lot of good use space for templates, especially given O365, but if they aren’t, well, deleting unused code is also good.

Why Hiring Is Awful, ATS Edition

These Are Awful Things

There has been a lot, a lot of bits spilled about how awful getting hired in Tech is. It’s not better anywhere, by the way, I’ve a friend wanting to get work as a plant manager. It’s just as awful for him. My son’s trying to get an entry-level gig not in tech. Awful for him too.

A bit part of the reason is the ATS system, the systems that, in theory, automatically ingest resumes and help automate and streamline the hiring process. There are two parts to ATS, the front end, the bits that the applicants deal with and the back end, or the HR side. This post is mostly talking about the front end. I’ve some experience with the back end side of things, and there are parts of that which work really well, like applicant tracking, where someone is in the process, etc. That part works great. The rest?

Failure

So in talking about ATS systems, you have to include the application websites. There’s no way, from the applicant point of view to separate them, so I shan’t. They fail. They fail in ways that honestly beggar the imagination. No really, they do. For example:

  • Job Title fields that only allow for 20 character title names. In some cases for references. I’m sorry, are you paying for storage by the byte?
  • Company name fields that cannot handle a slash or a parentheses. One of my not-too-long ago gigs has a slash in their name. That’s not optional, that slash is part of the name of the company. I have seen so many ATS systems implode trying to ingest that. This is not 1989 anymore, unicode is real.
  • If you have an Associate’s degree in something that isn’t what the ATS allows for, good luck entering that.
  • If you have college experience, but no degree, there are any number of ATS systems that don’t allow for that, so you either don’t include that experience, or you lie and put down a degree you don’t have because you can’t leave the field blank and “no degree” isn’t an option. Morton’s Fork should not be a design philosophy for any system.
  • I know it seems odd, but ten years later, I do not remember the day I started at a gig. Yet for too many companies, that’s important.
  • Why can’t I paste my email address in when creating an account? What is the benefit of this kind of thing?
  • Why is the county in which I live important?

I could go on, but you get the point. These systems are not only designed badly, they don’t work well, and that is perhaps not the impression one should get from a company that presumes to be competent at its job. Or anything.

The worst part of course, is that there’s no penalty for the companies that do this. Like ghosting, the company is allowed to be nigh-completely incompetent at all parts of hiring, but if the applicant misuses an apostrophe, well, they’re completely unqualified to be employed at any position whatsoever. One side must be perfect the other side doesn’t even need to show up.

I literally have yet to see, as an applicant, an ATS that ingested a resume correctly, or even mostly correctly. Ever. To the point that now, people recommend you basically create your resume in vi or some other text editor, solely, solely to increase the chances of the ATS system not losing its addled mind over you using a form of word processing created after 1990 or so.

I get that the assistance in tracking an applicant’s status is important. That part, in my experience works well, but it is literally the only part of every ATS that I’ve worked with or interacted with that works well. The rest is all hot garbage and should be buried next to the E.T. video game carts.

A Quickie on New Outlook Search

As I wrote in my first writeup on NOM (New Outlook Mac), I talked about the new search engine, and how it built KQL queries that were compatible with Outlook’s Web version. I just tested it, and yes, it is. Even cooler, if you modify a search in one and paste it into the other, it just works.

For example, I built this search string in NOM:

from:apple.com AND to:jwelch@bynkii.com AND subject:(AppleSeed) AND received>=2018-09-08

And got a listing of four/five emails from AppleSeed relating to various new builds. It pulled in results from multiple folders on that account, including the deleted items folder, which the old search didn’t do, I had to go into that folder to reliably search it.

I then opened my account in OWA, and pasted that string into the search bar there. Booyah! So then, I changed one of the terms from (AppleSeed) to (tvOS) and pasted that back into NOM. Booyah, now I only get emails with tvOS in them.

So that’s pretty cool, but what occurs to me is that this opens up some support avenues for people. If you’re really good with KQL and you have a user trying to do a really specific search and they’re having a hard time with it, you could build that search for them, send them the string, then they can just paste that into the search bar and voila! They find the thing they need.

Now, extending this, were say, the Outlook team to update the scripting dictionary a bit to allow for pasting from the clipboard, then you could provide this to the user as a script and they wouldn’t even have to manually copy and paste. They run the script and voila! They find the thing.

But wait, there’s more. So again, what if the Outlook team updated the scripting dictionary so that you could search more than just contacts with the new KQL search without pasting. And what if they allowed you, via the updated scripting dictionary to save that search as a saved search in the appropriate section (mail/calendar/contacts). That would be kind of handy if you’re having to move someone to a new machine, or you have some standard searches that people in a given group do a lot. Conversely, when they moved to a new machine, you could transfer any existing saved searches via script pretty easily.

You’d be able to give someone a new machine with all the saved searches they built, along with any searches you normally install. That would be kind of nice to be able to do for your users. And you could…if the Outlook Team were to update the scripting dictionary. (This is what I think about on the bus from the show location back to my hotel. Because I’m not quite right.)

Even better, you could use this to help build a maintenance/assistance script that would help someone look for “missing” emails/calendars/etc., create your own search scripts that could then take other actions based on the results of a search, search for specific attachment files and have other applications take actions on those files…if the Outlook Team were to update the scripting dictionary that is.

I feel there’s a theme here, but I can’t quite put my finger on it…

The Outlook is good

Outlook:Mac’s biggest update…well, ever

At Microsoft Ignite 2019, the Office team released an early-ish beta of the new Outlook client. If you’re on the Insider Fast track, you should be able to get it by the time this is posted.

As some background, I’ve been a part of working with and testing Outlook on the Mac since it was called “Entourage” and really, since Entourage was called “Alpaca” because it didn’t yet have a name. And yes, I know, Entourage and Outlook are in fact different products. Relax, it’s all good. This is just establishing timeline of how long I’ve been working with and help test this product.

I don’t think it’s hyperbole to say this is the biggest update to Outlook on the Mac since 2011. Possibly since the short-lived Outlook 98.

At the heart of these changes is the “new” sync engine. I say “new” because it’s not really new, it’s the same sync engine used in other Microsoft products, such as Outlook on iOS and the mail and calendar clients in Windows 10. Yes, that’s right, EWS is dead. Completely dead. It is an ex-protocol.

According to the team, this is really important, because it allows them to radically increase the “improvement velocity” of Outlook. This was a problem when Outlook was using EWS, as that protocol was considered “done” and wasn’t going to get new features anytime soon.

In terms of immediate benefit, I’ve seen two: first, prior to New Outlook Mac (NOM…DON’T YOU JUDGE ME), if I replied to an email in Outlook, that message would show that I’d replied to it. If however I replied say, from iOS’s Mail app, then Outlook wouldn’t show that. Not a huge deal in and of itself, but that kind of linkage in the UI is really convenient. With NOM, even when you reply from iOS Mail, Outlook will reflect that.

I was straight. up. giggling over that. Because it removed such a silly pain point.

In addition, Outlook’s resource usage has dropped off a cliff. I was used to seeing Outlook, regularly, on my 2019 MacBook Air, eating over 100% of available CPU in Activity Monitor. With NOM, I have to search for Outlook in Activity Monitor. At most, I’m seeing around 40% or so. Disk I/O looks to be similarly improved. That’s a big deal for people who care about battery life. Like, you know, everyone on a laptop.

In terms of syncing, and keep in mind, this is a very early release. For example, on-prem Exchange and iCloud accounts still use the old sync engine. I plan to test it once I’m back home and on stable Wi-Fi so I can give y’all some real-world numbers.

UI Changes

The UI has had a fairly extensive change. First, the Ribbon is gone. Like gone. As with EWS, it is an ex-UI element. I personally never had a real problem with the Ribbon, I honestly kind of like it, but I’m a very edge case users. (Long ago, I had some weird stuff using rules kicking off scripts as mailing list folder reapers. I am not an average user.) The new UI is very clean and nicely customizable:

New Toolbar. No More Ribbon in NOM

Obviously, NOM supports Dark Mode. The new toolbar is also fairly customizable:

Toolbar Customization Options

The look is really clean. One thing I’d like to see change in upcoming versions is an ability to increase the font size for toolbar labels. Some of us are not gifted of perfect vision.

Search changes

While Outlook’s search has been solid, there were a lot of cases where it would miss things. As well, being based on Spotlight, if you added a new account, there was a delay before you could use the search effectively while indexing was happening. With NOM, Outlook is now using the MS Online search engine instead of Spotlight. Along with what they say is better overall search performance, there’s a few neat new features this brings:

First, the search is supposedly faster and better. I’ve not had a chance to test this out, not extensively, but it doesn’t feel any slower. It’s also fully usable much faster than the Spotlight-based search was. The search bar itself is now much larger and central in the window. Secondly, they’ve also created a nice in-between UI for when you need a bit more than just typing in a text field, but you don’t need the full-on advanced search.

New search bar, still supports “classic” email search syntax
New search dropdown

The new dropdown is accessed by the funnel icon in the search bar. But I want you to notice the search syntax in the search bar over the dropdown. I’ve pasted it in below:

from:events@floridatheatre.com AND From: "Florida Theatre" AND subject:(yourself) AND received>=2019-10-08

That syntax is the same syntax you’d use in Outlook on the Web. That’s kind of handy. It’s based on MS’s KQL language. This is a fairly rich query language that allows for far richer searching than the traditional searching one did pre-NOM. The way the drop-down builds the query is kind of neat and once the full advanced search UI is attached, (Neither it nor saved searches are currently functional in this version), there’s a potential to do some neat work in building custom searches for people.

Another UI change is related to conversation. Now, when viewing by conversation, it pulls in emails from all folders, not just the current one, a convenience for those with complex folder structures (like me, which is ironic, as I never use conversation view.) There is also an “Ignore Conversation” option, but be aware, “Ignore” in this context moves the conversation into Deleted Items. This is kind of suboptimal, and could be problematic, and I would like to see this behavior change moving forward. In the current release, you can’t not use conversation view, something that again, is a beta issue.

(Note: This really is a beta, not just a “sort of not done” drop. There’s a lot that needs to be done, so NOW is the time to get your feedback in, things are still fluid.)

The Return of My Day

The fogies among the Outlook:Mac users will remember My Day, a related, but separate application that showed you, well, your day. It was a floating window that showed you what was going on in your calendar for that day. I really liked it and have missed that feature since it went away with Outlook 2016. Is it a real pain to alternate between cmd-1 and cmd-2 to switch between mail and calendar views? No, not really, but it’s always been one of those “why is this necessary” things. Like being nibbled to death by baby ducks.

With NOM, My Day is back. It’s not a separate window, it lives on the far right of your mail window and can show you your agenda or your day:

Agenda view (L) and Day View (R)

This is one of those things that isn’t really necessary, but it saves me a lot of time over the course of the day a few seconds at a time, and it means I don’t have to switch out of my current context just to see what’s happening next in my calendars. My Day can show you as many or few of your calendars as you like.

Talking to some of the Outlook folks, they’re looking at, in a future update, making My Day accessible via the Menu Bar, so you could see your appointments and schedule without having to open Outlook. This would be nice, especially when you’re working on something that isn’t email and you don’t want to get drawn into email just because you need to check your schedule.

Another new feature, not yet wired in, is the ability to reply to a message without having to pop a new message window. Calendar invites have a new UI too, giving you the information you need right there in the invite window:

New Event UI

At some point in the near future, this will integrate with Teams, so you can use that as an option for a meeting in addition to or instead of physical rooms. After the session, a few of us asked for more intelligent Out Of Office statuses, like “Working Somewhere Else”, which would allow someone who was working at a different location to, when they get the meeting invite, not even see a physical location for the “location” but rather the “Join Teams Meeting” button. Creating the event would also use this, so that the event creator would know that some of the meeting attendees would not be able to be there in person, and would be using Teams. A bit nicer than the blunt weapon that “out of office” currently is. (Again, if this is something you’d like to see, get your feedback in now.)

Calendar UI changes

Along with the mail UI, the calendar UI has gotten a lot of love. The default toolbar has been greatly simplified:

New Calendar toolbar

Note that the Temperature gets front and center, with the Day/Week/Month view picker in a dropdown to the right. The widget next to that allows you to view My Day in the calendar as well. I’m not sure how amazing that is, but sure, why not. Actually, I accidentally figured out why this is cool. You can have a different set of calendars displayed in My Day than you have displayed in the main calendar window. That’s cool.

Event creation has been greatly simplified in NOM. For simple events, you just drag and drop in the calendar window to select your meeting times on a given day, ala the macOS Calendar app, which then gives you a popup that allows you to quickly set up a simple meeting:

Quick Meeting Popup

A change that I really appreciate is the much, much easier methods NOM has for changing calendars for a new event. It’s literally just a pulldown in the top of the new meeting popup:

SO MUCH NICER

It’s pretty simple and works well for simple needs. But if you need more, the “real” event creation window has also gotten a revamp:

Full Event Creation Window

The easier calendar selection is there as in the popup. Also note that the repeating settings have been moved to a more central location, rather than up at the top where it was a bit easy to miss. Teams meeting selection is right there as well. One thing of note is the calendar column on the left. Note that the current schedule shows as green. If that time conflicted with another event, that would be red, (and I did suggest, given the prevalence of color blindness in the population, that Outlook not solely rely on color for this functionality.) You can change the meeting time and duration in that window to help you find a more compatible time if needed. The traditional scheduling assistant is still available for when you have a lot of attendees with complicated schedules.

One thing I want to point out is the little sun icon in the upper right part of the main window next to the calendar column. That’s in the email window as well, and it allows you to easily toggle the text area of the meeting (or message) from a dark background to a light background and back. A nice touch.

Again, this is a really early release beta. There’s a lot of work to still be done. For example, the Contacts pane doesn’t work at all. You click on it, you get a sheet telling you you have to switch back to old Outlook for that. It is very much not done.

But I’m still really impressed with where NOM is going. The new sync engine will enable a lot of things that just weren’t going to be possible before, like voting. Better syncing with iCloud was brought up and they are aware of the need, and from what the Outlook folks were saying, this should be significantly easier to implement. (Given the Google account improvement updates, I think any political issues went away some time ago.)

There’s some other missing features, like “resend”, but that should be coming back. “View Source” is also missing with accounts using the new sync engine and I pointed out that while not used by a lot of people, for user support/mail support people, being able to see the raw headers of an email is a critical need. Again, if this is important to you, let the team know.

One interesting statement during the session was that matching the Windows client feature for feature is not a goal in and of itself. (Obviously, there’s no ribbon any more.) Before everyone gets all weird, there are windows features, (Like VBA scripting) that not only don’t make a lot of sense on macOS, but are actually dangerous, (like VBA scripting.) “It’s in Windows” is not a great reason to implement a feature. “This is a thing I need and my users need because <reasons>” is a better way to ask for a feature.

I did remind them that Outlook’s scripting implementation has been stagnant since 2011, and it shows and they should update that. Additional feedback on that would be a help in getting them to up the priority for that.

To use NOM now, you currently need to a) be on macOS 10.14 or later and b) be on the Office Insider Fast Track. The Insider Fast requirement will eventually go away, the 10.14 and later, I don’t see that changing.

NOM is not done yet, there’s still a lot of oddities because of its unfinished state, but it’s like the pause between the time a pilot shoves the throttles into afterburner, and the plane starts hauling ass. For the first time in a while, there’s a clear path for some real improvements in Outlook, and I am so here for it.

From AppleScriptObjectiveC to Swift pt. 2

Today kids, we get some REST…

Okay, the joke was right there, and no, there is no joke “too bad” for me to take. So in the previous post, we looked at initial defaults setup, both in ASOC and Swift. We didn’t do anything with them, but we have the initial setup done. There’s a few things we have to deal with outside of that before we actually get into defaults or UI setup.

Also, for everyone who says “just rewrite it in <different language I like better>, note that this series will be rather long before we get to what the ASOC version has today. There’s going to be a lot of work done to get to where we are right now. No new features. Just a lot of work to get to where we are today.

Nagios Manager interacts with Nagios servers via a REST API. So I send URLs with data and get data back. Sometimes I’m reading existing information, (GET), creating or adding information (POST) or removing information (DELETE). But everything revolves around that.

Within Swift, there’s two major components I’m using, at least initially: URLSession and JSONSerialization. The former handles the communication between the app and server(s), the latter handles parsing it out into a useful format, usually a dictionary, or NSDictionary in ObjC-land. While I do use JSONSerialization in ASOC, for the URLSession components, I “cheat” as it were, and use a handy trick from AppleScript: the “do shell script” command, which lets me just execute curl commands from the shell.

I could use URLSession within ASOC, but I never did, “do shell script” is simpler, although probably not easier, and it lets AppleScript handle some things for me automagically. So as before, I’ll split this post up into the ASOC code and the Swift code, with explanations of each. This post will be focused on the GET part of the communications, we’ll take on POST and DELETE later on.

Also, while one downside of Swift is that it does’t have a tool like Script Debugger, it does have playgrounds, which gives me some of the tools Script Debugger has. (There’s no proper debugging in playgrounds, and that is an actual shame, it would make them even more cool than they are.) Playgrounds are awesome for testing out code before putting it in the app.

Nagios REST URLs

Before we get into the code, an overview of how URLs are used within the Nagios REST API. Regardless of what a given URL is doing, they’re all broken down about the same way. There’s a “root” URL, a path that indicates the main section of what the URL is for, an API Key, and then the specific command properties. GET/POST/DELETE are used to indicate the primary action. In this example, I’m using a ‘system info’ URL, as it returns a small amount of data that’s easily used to ensure I’m getting the info I need in the correct form. By and large, this is a very large string that I end up coercing/converting into a Dictionary (Swift) or a record/NSDictionary (ASOC) so I can make better use of key-value pairs.

Here’s a sample of the URL, (sanitized, obvs):

http://<ipaddress or DNS name of server>/nagiosxi/api/v1/system/info?apikey=<very long, complex API key>&pretty=1″

That’s pretty simple, but it has the basics. In terms of application logic, there’s a handful of parts. The first is the IP address or DNS name of the server. I trust I don’t have to explain that one. The second is the /nagios/api/v1/ part. that will go after the IP address or DNS name of the server for every command, and we’ll use that as a constant in Swift, once we get going. The system/info? bit identifies what part of the API the URL is talking to. In this case, basic system info. For user functions, we’d use “system/user”, and for host functions, “objects/host”. The API key is the authentication mechanism for a given server, and why I may add one new feature into this, if it’s not that hard, namely encrypting that data. Maybe. (Security in monitoring software is a mess on a good day. But if I can manage to do the right thing, I try to. I’m not a very good programmer.)

Everything after the API key is the actual command parameters. In this case, there are none, one reason it makes for a useful test case. Finally, the “pretty=1” bit makes the return look like this:

{
“product”: “nagiosxi”,
“version”: “5.6.5”,
“version_major”: “5”,
“version_minor”: “6.5”,
“build_id”: “1563463555”
“release”: 5605
}

Instead of a single undifferentiated string. Much easier to read and use, especially for setting up key-value pairs.

ASOC communications

Here’s the basic code for sending a GET request and putting the results into a record (dictionary) in ASOC:

property theSMTableServerURL : "" --bound to server url column in table
property theSMTableServerAPIKey : "" --bound to server API Key column in table
====================================================================
set theSMSURL to theSelectedServer's theSMTableServerURL as text
set theSMSURL to current application's NSString's stringWithString:theSMSURL
set theSMInfoURL to theSMSURL's stringByAppendingString: "system/info?apikey="
set theSMServerInfoCommand to "/usr/bin/curl -XGET \"" & theSMInfoURL & theSMSelectedAPIKey & "&pretty=1\"" 
set theSMServerInfoJSONDict to my getJSONData:(theSMServerInfoCommand)

====================================================================
on getJSONData:theCurlCommand	
	set theReturnedJSON to do shell script theCurlCommand
	set theReturnedJSON to current application's NSString's stringWithString:theReturnedJSON
	set theReturnedJSONData to theReturnedJSON's dataUsingEncoding:(current application's NSUTF8StringEncoding)
	set {theReturnedJSONDict, theError} to current application's NSJSONSerialization's JSONObjectWithData:theReturnedJSONData options:0 |error|:(reference)
	return theReturnedJSONDict
end getJSONData:

There’s really not a lot going here. The three sections offset by the “===” line are both parts of using curl in ASOC and in the app.

The first part is setting up the two properties we use. The server info, including URL and API key are in the server manager table in the UI. (Really, it’s in an array controller, but there’s no real code to demo that bit. At least not yet.)

theSMTableServerURL property is the IP address or DNS name of the server with “/nagiosxi/api/v1/” appended to it. All Nagios REST commands use that, so there’s no point in not storing it that way. Saves time.

theSMTableServerAPIKey property is the API key used for that server, also in the server manager table.

set theSMSURL to theSelectedServer's theSMTableServerURL as text

Is me doing two things. First, I’m avoiding manipulating the property directly. Secondly, I’m making sure I know theSMSURL is text. (AppleScript’s version, not Cocoa’s.) I found that ASOC has an annoying tendency to sometimes make this particular value text and sometimes it’s an NSSTring. The behaviors of both are rather different within ASOC, so I remove the uncertainty.

set theSMSURL to current application's NSString's stringWithString:theSMSURL

Is me converting AppleScript text to a proper NSString. We’ll need that, and again, this way I know what SMSURL is in terms of type.

set theSMInfoURL to theSMSURL's stringByAppendingString: "system/info?apikey="

Since NSString isn’t mutable, and NSMutableString gives me the irrits and this app is not time-sensitive in terms of execution, meh, create another NSString, but with the bits that make it a “I want basic info on the Nagios Server” command, along with the APIKey lead-in.

set theSMServerInfoCommand to "/usr/bin/curl -XGET \"" & theSMInfoURL & theSMSelectedAPIKey & "&pretty=1\""

Here’s where we build the full curl command. Since the command itself as used requires quotation marks in the command, we add those in by escaping them. the “&” is AppleScript’s string concatenation symbol. So when this is all done, theSMSServerInfoCommand is fully set up to send a request for basic server info to the Nagios server.

set theSMServerInfoJSONDict to my getJSONData:(theSMServerInfoCommand)

Here’s where we call the getJSONData function, or in AppleScript-ese, the handler. We’re passing it theSMServerInfoCommand string, and getting back theSMServerInfoJSONDict, which will be an NSDictionary, or AppleScript Record.

Now for the function. There’s not a lot of code, but there is some stuff happening. ASOC, as a rule, is slaved to Objective-C, so the calling conventions vaguely resemble ObjC, but without all the brackets and @-signs. I think it’s a bit more readable.

on getJSONData:theCurlCommand

The initial function definition. Note that we don’t have to specify the type of data theCurlCommand is. It’s just there, we either use it correctly or we do not. Handy in some ways, less-handy in others. Definitely lets you be lazy, so I like it. Life is too short for a high-level language not to do work for you.

set theReturnedJSON to do shell script theCurlCommand

This is the “do shell script” command, which lets us run curl in the shell environment. It uses sh, not bash or zsh. That can trip you up. It also doesn’t use your .profile or .zshrc files, so you really want to fully path everything. Really. Really.

The result of the command, assuming no error is set into theReturnedJSON. It may not actually be JSON, strictly speaking, but I have my theOwn theNeuroses with variable naming, and I’m the only one having to read this, so meh. What you get back looks like this:

{
“product”: “nagiosxi”,
“version”: “5.6.5”,
“version_major”: “5”,
“version_minor”: “6.5”,
“build_id”: “1563463555”
“release”: 5605
}

(just as a reminder)

set theReturnedJSON to current application's NSString's stringWithString:theReturnedJSON

Since do shell script is an AppleScript command, it returns AppleScript text. We need it to be an NSString, so we convert it to one. Since AppleScript is somewhat unconcerned about many types of coercion, we don’t have to use a different variable. theReturnedJSON used to be AppleScript text, now it is an NSString.

set theReturnedJSONData to theReturnedJSON's dataUsingEncoding:(current application's NSUTF8StringEncoding)

since NSJSONSerialization wants NSData and not NSString, we have to convert it to NSData here via NSString’s dataUsingEncoding method and NSUTF8StringEncoding as the specific encoding process. We do use a new variable here, so I know this is the NSData version of theReturnedJSON.

set {theReturnedJSONDict, theError} to current application's NSJSONSerialization's JSONObjectWithData:theReturnedJSONData options:0 |error|:(reference)

This returns an NSData record of arrays and shoves it in theReturnedJSONDict. Technically it’s an NSJSON object, but really, it functions more like an NSDictionary. Each line is a key-value pair, and this also allows it to work like an AppleScript Record, which is really handy for ASOC.

return theReturnedJSONDict

return theReturnedJSONDict to the calling function.

end getJSONData:

End the function/handler. the “:” functions as a “()” would in other languages. This is inconsistent mind you, sometimes, you’ll see “foo:()” or even “foo()“. The ASOC bridge is sometimes a tad odd.

Swift Communication

So some caveats here. The Swift code is very much playground code. It will look different in the final version, and once I get to where I feel like setting up the github repo for it, you’ll be able to see that. Again: I am not a full-time programmer, nor am I a Swift genius. I am very much a pickup truck programmer. It ain’t fancy, probably slower than it could be, but I can read it and understand it. It’s similar to how I play D&D: I understand the tricks I use as a monk very well. I don’t use a lot, but the ones I use, I really understand. Basically, I’m not clever. At all.

Also, many, many thanks to https://learnappmaking.com/urlsession-swift-networking-how-to/ for that page. It made it so much easier to figure out WTF was going on.

import Cocoa

//many thanks to https://learnappmaking.com/urlsession-swift-networking-how-to/ for this info.

//build the components to get data from nagios
//you can't use URLComponents with this, the encoding causes problems.

//we may be using url = url?.appendingPathComponent("users") kind of thing in the future

//this returns a small number of things from the nagios server
//we'll probably want this to be a var when we build it, made up of different items.

let theURL = URL(string: "http://(ipaddress or DNS name of the server)/nagiosxi/api/v1/system/info?apikey=(really long API key)&pretty=1")!

//create a URL Session object
let theSession = URLSession.shared

//theTask creates a data task. The parts are as follows:
//data - the actual JSON data we get from the server
//response - various response codes, mime types, etc
//error - any errors thrown
let theTask = theSession.dataTask(with: theURL) {data, response, error in
	
	//if we get an error or no data, bad. Probably won't use this in proudction
	if error != nil || data == nil {
		print("Client error!")
		return
	}
	//check for valid response code. Again probably won't use this
	guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
		print("Server error!")
		return
	}
	
	//check for the right mime type. Since we know how nagios returns things, not really needed
	guard let mime = response.mimeType, mime == "application/json" else {
		print("Wrong MIME type!")
		return
	}
	
	//this parses the returned data and turns it into  a proper JSON object
	do {
		//store contents of theURL as a Data variable
		let theData = try Data(contentsOf: theURL)
		
		//get the results as a dictionary, not a string
		let theJSON = try JSONSerialization.jsonObject(with: theData, options: []) as! [String:Any]
		
		
		//note that release version comes as an int from the rest api, not a string:
		/*
		{
			"product": "nagiosxi",
			"version": "5.6.5",
			"version_major": "5",
			"version_minor": "6.5",
			"build_id": "1563463555",
			"release": 5605
		}
		*/
		
		//set up the variables to extract the data from the dictionary
		var theProduct: Any
		var theBuildID: String
		var theVersion: String
		var theRelease: Any
		var theMajorVersion: String
		var theMinorVersion: String
		
		//pull our vars from the dictionary
		//since these could be nil, we force unwrap at theJSON and use as! to force downcast AnyObject to string
		//or Int as needed
		
		//it is probably a good idea to make sure your variables are what you think they are
		//this can avoid problems later. But this does show you have options if you're not sure
		//what the return will actually be
		theProduct = theJSON["product"]! //as! String
		theBuildID = theJSON["build_id"] as! String
		theVersion = theJSON["version"] as! String
		//even though we created the var as an Any, we force it to be an Int here. 
		theRelease = theJSON["release"] as! Int
		theMajorVersion = theJSON["version_major"] as! String
		theMinorVersion = theJSON["version_minor"] as! String
		
		print(theProduct)
		print(theBuildID)
		print(theVersion)
		print(theRelease)
		print(theMajorVersion)
		print(theMinorVersion)
		
		//print(theJSON)

}

theTask.resume()

There’s a lot going on here. Well, not really, but it took me a while to get it. (That was reason why I didn’t use NSURLSession in the ASOC version. It was weird to learn. Well, also, I didn’t have to.)

Some of it, like the setup for the print statements is test code, so I know what’s going on with it. That most likely won’t be that way in the final version, but it is useful here.

let theURL = URL(string: "http://(ipaddress or DNS name of the server)/nagiosxi/api/v1/system/info?apikey=(really long API key)&pretty=1")!

Since this is a constant, we use “let”. This will eventually be built the way it is in the ASOC version. But for now, we just hardcode the whole thing.

let theSession = URLSession.shared

Create a simple URLSession object. Should I need to get more complicated, I will, but not until.

let theTask = theSession.dataTask(with: theURL) {data, response, error in

Create a data task that when run, gives us three things:

1) data, the actual returned JSON data
2) response, the HTTP response info wif error != nil || data == nil {ith things like return codes, etc.
3) error, I feel this is self-explanatory. At best, we’ll only care if it’s not nil.

Maybe not even then. Nagios Manager isn’t the kind of thing you just run casually. I can make more than a few assumptions.

if error != nil || data == nil {
print("Client error!")
return
}


If we get any kind of error or no data, print an error message and return.

(can I just say wordpress’s block editor handling of returns is annoying as hell. No you goddamned schmuck, just because there’s a return when I paste something in, that does NOT mean I want it in a separate block. Ye gods.)

guard let response = response as? HTTPURLResponse, (200…
299).contains(response.statusCode) else {
print("Server error!")
return
}


if we don’t get a “correct” response code, do error things

guard let mime = response.mimeType, mime == "application/json" else {
print("Wrong MIME type!")
return
}


If the MIME type is wrong, do error things.

do {
//store contents of theURL as a Data variable
let theData = try Data(contentsOf: theURL
let theJSON = try JSONSerialization.jsonObject(with: theData, options: []) as! [String:Any]


Here, I’m setting up “Data” as a Data object version of theURL. Since the “contents” of the URL is the returned data, which looks like a big string to swift, we want it to be a dictionary, with the keys being String and the values being Any, as we get multiple return types. This is kind of a pain, since we don’t get ASOC’s “I’ll handle that for you” features, but it’s not awful. (I may be wrong about what this code does. But it does work the way I expect it to work, so close enough. URLSession is weird to me.)

//set up the variables to extract the data from the dictionary
var theProduct: Any
var theBuildID: String
var theVersion: String
var theRelease: Any
var theMajorVersion: String
var theMinorVersion: String


So out of all of these, only theRelease isn’t.a string, it’s an int. This is me playing a bit with Swift, because while I’ve read books and done stuff, this is the first time I’m actually using it.

//it is probably a good idea to make sure your variables are what you think they are
//this can avoid problems later. But this does show you have options if you're not sure what the return will actually be

theProduct = theJSON["product"]! //as! String
theBuildID = theJSON["build_id"] as! String
theVersion = theJSON["version"] as! String
theRelease = theJSON["release"] as! Int
theMajorVersion = theJSON["version_major"] as! String
theMinorVersion = theJSON["version_minor"] as! String


If you can’t tell, I talk to myself a lot in comments. I also walk my way through problems that way. Sometimes I put recipes in my comments. I’m odd like that. This is just me extracting things from a dict and documenting it so when I do it later, I know how. Note me coercing things. Except for theProduct, which was a “I wonder what will happen if I leave it as Any. Turns out, not much. But I won’t do that in the app, seems like a bad idea.

print(theProduct)
print(theBuildID)
print(theVersion)
print(theRelease)
print(theMajorVersion)
print(theMinorVersion)


PRINT ALL THE THINGS

} catch {
//print any errors
print("JSON error: (error.localizedDescription)")
}


Do error things.

}
theTask.resume()


Actually run the task, and get the JSON that lets us do the things. Y’all, I get that it works and it looks the same in ObjC, but this is kind of weird. Just saying.

As I said, this is not how it will look in the app, but it helps me see how swift does things. I’ll probably wrap up the JSON code in its own function and maybe even wrap up the URLSession code in its own function. Or not. Depends on how much of a pain in the ass this is.

The next bit of this will probably delve into the initial UI creation. Unless it doesn’t. And if you read this, thanks!

From AppleScriptObjectiveC to Swift

Oh this will be fun…

Recently, I decided to try to update my Nagios Manager app from AppleScriptObjectiveC to Swift. There’s a few reasons for this, one, it takes a lot less time to type “Swift” than “AppleScriptObjectiveC”. I mean, I can use the more common “ASOC”, but that doesn’t google as well. Secondly, there’s a few things I’d like to do that are a real pain in the ass to do with ASOC, and some that just won’t work.

I don’t want anyone to take “John is dumping ASOC” from this. I’m not. I like ASOC, and it’s a good higher-level language. But there are things it can’t do well, and Swift does those things well.

Note: I will not be using SwiftUI. It would take a month to get Nagios Manager’s UI working in SwiftUI, and in any event, if this were to ever move to iPads/iPhones, I’d have to redesign the UI completely for them anyway. For now, this is a macOS-only app, and as I’ve talked about before, SwiftUI is a really awful choice for macOS apps that aren’t also going to be on iOS/iPadOS

UserDefaults

Nagios Manager stores its settings in User Defaults. It’s a plist that’s an array of dicts with a boolean I use to see if there’s anything there other than the boolean. I’m kind of lazy and if I use UserDefaults, then I don’t have to deal with paths or locations. It may not be the best way to do this, but it lets me write less code. I like writing less code. The initial ASOC version is:

property theDefaults : missing value --referencing outlet for our NSDefaults object
property theSMSettingsList : {} --settings list array
property theSMDefaultsExist : "" --are there currently settings?

set my theDefaults to current application's NSUserDefaults's standardUserDefaults() --make theDefaults the container for defaults operations

my theDefaults's registerDefaults:{serverSettingsList:{}} --sets up "serverSettingsList" as a valid defaults key  changed to more correctly init as array instead of string. It also deals with nils much better

set my theSMSettingsList to (my theDefaults's arrayForKey:"serverSettingsList")'s mutableCopy() --this removes a bit of code by folding the NSMutableArray initialization and keeps it mutable even after copying the contents of serverSettingsList into it.

 set my theSMDefaultsExist to theDefaults's boolForKey:"hasDefaults" --get the boolean value for the hasDefaults key

 if not my theSMDefaultsExist then --if there are no defaults, let the user know this so they can fix that issue.

try --to catch the error -1 

display dialog "there are no default settings existing at launch" --my version of a first run warning. Slick, ain't it.28 when a user hits cancel

on error errorMessage number errorNumber 

if errorNumber is -128 then
--this error is generated when the user hits "cancel" in the display dialog. This on error sinkholes it that so it doesn't cause any actual problems, since the end result for either Cancel or OK is what we want.

 end if
 end try 

set my theSMStatusFieldText to "If you're seeing this, then there's no servers saved in the app's settings. This tab is where you add them.\r\rYou'll need three things - the server's name, URL and API Key. For the URL, only the first part, i.e. https://server.com/ is needed. The \"full\" URL is generated from that.\r\rThe app itself is pretty simple. You can add or remove servers. Those are saved locally on your mac.\rThose servers are used to pull down user info in the User Manager tab. More info is in the application help in the Help Menu."

else if my theSMDefaultsExist then --there's no point in running loadServerTable: if there's no data to load

 my loadServerTable:(missing value) -- initial load of existing data into the server table.

end if

Yeah, there’s a lot going on there, especially if you aren’t used to ASOC or AppleScript. Also, I really like to comment the crap out of my code.

The first three lines are setting up properties, which are…an odd form of global variable that’s not really a global. I probably abuse them, but for ASOC, properties are your very good friend. They’re initially set up as empty strings, one empty list/record which corresponds to an array/dictionary in Swift and one missing value, which probably comes closes to an NSObject. We mostly use them in ASOC for UI binding, but we use it here for creating our initial NSUserDefaults object.

One of the weird things about going from ASOC to Swift is that ASOC is rather tightly bound around ObjC (you can use ObjC in an ASOC app), but not Swift. So there’s some translational work you have to do there, like Array instead of NSArray, etc.

The next statement creates our UserDefaults object:

set my theDefaults to current application's NSUserDefaults's standardUserDefaults() --make theDefaults the container for defaults operations

There’s some ASOC-isms there that need to be dealt with. The “my” keyword is a way of dealing with scope. If I didn’t have the “my”, I’d have to have theDefaults defined in the function it lives in, on applicationWillFinishLaunching:aNotification specifically. (“on” is another ASOC keyword, ala “func” in Swift.) The “my” allows me to use the property version of “theDefaults”. Oh, and “--” is how you do single line comments in ASOC and AppleScript.

current application's” is another ASOCism, which relates to how generic AppleScript does things. AppleScript is really designed to be an application scripting language, where you use different applications as engines to do work. AppleScript in and of itself isn’t really designed to solve all your problems for you. So the “current application” thing is needed so the ASOC runtime knows what you mean. This statment is saying: “Set the value for theDefaults” to be an instance of NSUserDefaults.” This will be how we are able to use NSUserDefaults methods. This is why we defined theDefaults as missing value in the properties statement.

Next is:

my theDefaults's registerDefaults:{serverSettingsList:{}} --sets up "serverSettingsList" as a valid defaults key changed to more correctly init as array instead of string. It also deals with nils much better

This is one of those cases where a higher level language is much nicer to work with. I don’t have to declare much about serverSettingsList other than it’s some form of array-ish object. This will let it work as either a record (dictionary) or list (array). It’s kind of nice. I could have used a string or what have you here, but it turns out I need an array-ish object anyway, and it handles nils better than strings or other variable types.

Next is:

set my theSMSettingsList to (my theDefaults's arrayForKey:"serverSettingsList")'s mutableCopy() --this removes a bit of code by folding the NSMutableArray initialization and keeps it mutable even after copying the contents of serverSettingsList into it.

So within theDefaults, serverSettingsList, as it turns out, is an array of dicts, or in ASOC terms, a list of records. There’s a few dicts in there, one per nagios server and some for the AD Auth Records Nagios Manager needs to use. So what we do here is say “Shove all the data in serverSettingsList into theSMSettingsList array-ish thing, (which then makes theSMSettingsList a list of records), but also make it an NSMutableArray, because we’ll potentially need to modify it at some point.

Next we want to see if there are any defaults currently on-disk:

set my theSMDefaultsExist to theDefaults's boolForKey:"hasDefaults" --get the boolean value for the hasDefaults key

Now, I could have checked to see if the results of the mutableCopy() were nil, but, that’s only going to be the case if there’s nothing. Within Nagios Manager, there is the ability to delete one, or more, or all servers and settings for those servers. However, that doesn’t delete the defaults plist file. So we could have an empty array, which wouldn’t necessarily return nil, even though there’s no useful data in it. By checking the value of the “hasDefaults” boolean, I get a better answer, one of three:

  1. There’s a defaults file, but no servers listed (false)
  2. There’s no defaults file, (false/nil or the ASOC version of nil, missing value.)
  3. There are defaults in the file, (true)

That reduces down to one of two return values, true/false, which make checking easy. The next part is an if-then which handles this:

if not my theSMDefaultsExist then --if there are no defaults, let the user know this so they can fix that issue.

In Swift, this would be “if !theSMDefaultsExist”. If this happens on launch, there’s no defaults available to load, so let’s tell the user that. We’ll do that via a display dialog statement that will let the user know there’s no defaults to read. We’re going to wrap that in a try block because when you hit the “Cancel” button in a display dialog’s dialog, that generates an error (-128) that we want to trap and sinkhole, since we don’t really do anything based on the response. This is jsut for notification purposes to the hu-mon:

try --to catch the error -1

display dialog "there are no default settings existing at launch" --my version of a first run warning. Slick, ain't it. Generates a -128 when a user hits cancel

on error errorMessage number errorNumber

if errorNumber is -128 then
--this error is generated when the user hits "cancel" in the display dialog. This on error sinkholes it that so it doesn't cause any actual problems, since the end result for either Cancel or OK is what we want.

end if
end try


That’s a basic try-on error block. I could have just left the on error part blank, but just in case there’s ever a different error I want to handle differently, I’ve left a structure in place to do that. As I said, this is a big sinkhole for that error.

This next text block:

set my theSMStatusFieldText to "If you're seeing this, then there's no servers saved in the app's settings. This tab is where you add them.\r\rYou'll need three things - the server's name, URL and API Key. For the URL, only the first part, i.e. https://server.com/ is needed. The \"full\" URL is generated from that.\r\rThe app itself is pretty simple. You can add or remove servers. Those are saved locally on your mac.\rThose servers are used to pull down user info in the User Manager tab. More info is in the application help in the Help Menu."

Is used in the UI for Nagios Manager to help explain more of what’s going on. When you see the Swift code, you won’t see any of this or the swift version of the Display Dialog thing, since I haven’t actually started to build the UI yet.

else if my theSMDefaultsExist then --there's no point in running loadServerTable: if there's no data to load

my loadServerTable:(missing value) -- initial load of existing data into the server table.

end if


If the defaults exist, then we call the loadServerTable function, which loads the array controller that manages the list of servers in the UI.

Since we aren’t passing it any values, it gets a (missing value). One of the reasons for using so many properties is there are things that don’t exist in ASOC, like structs, so where you’d normally pass stuff in other languages, I find it much, MUCH easier to use properties instead.

So now, here’s my first pass at the Swift version:

//create the defautls object
var theNMDefaults = UserDefaults.standard
//the var we'll use to actually hold defaults in the app
var serverSettingsList = [String: String]()
 //register serverSettingsList as a valid defaults key
 theNMDefaults.register(defaults: serverSettingsList)
 //pull the defaults, which ends up being an array of dicts
 var theNMSettingsList = theNMDefaults.array(forKey: "serverSettingsList")
 //check to see if defaults exist at all. Even though 
 //theSMSettingsList will be nil if there's NOTHING as in no 
 //plist at all, there's also the case of the file existing, 
 //but we erased the settings or what have you. So we check 
 //the bool key we use for when the file exists but there's 
 //no settings in it. We're also doing some LIGHT variable 
 //renaming. 

//this will be false if there's nothing there or set to 
//false
 var theNMDefaultsExist = theNMDefaults.bool(forKey: "hasDefaults")

//initial if block
 if !theNMDefaultsExist {
 //display alert stating there are no defaults to read
 } else {
 //do initial loading of defaults into the appropriate 
 //controller. That will happen after we start building out 
 //the UI.
 }

So this block isn’t as complete as the ASOC version, but it’s doing the same basic stuff. Creating a user defaults object, registering with the object, checking to see if there’s any defaults, and setting up the actions if there’s anything there.

So that’s the first bit. This will be a long project, and I’m not working on it every day. (For one, it’s about impossible to test from home.)

I also plan on making all kinds of mistakes, because I’m not a full-time coder, I’m certainly not an expert in swift, and really, this app has a design target of me. So there’s things I’m doing here that someone with more of a clue wouldn’t, but meh. This app works by slinging REST commands back and forth and parsing JSON. Algorithm speed is not that essential here. I can also see that I’m going to miss a lot of the things ASOC did for me as a higher level language.

The next step will be the initial UI setup, a window with 4 tabs, and to get the initial setup working for the server manager part. So fun times.