February 1, 2010

It's not CPU percentages Adobe

So one of the big memes Adobe is pushing in the endless Flash whining is that if mean old Apple would just give them the low-level APIs they need, they could do all this wonderful acceleration, and all that CPU utilization the Flash plugin needs would go away, and life would be fantastic.

This of course, is bullshit, and it's pandering bullshit to boot. CPU utilization, expressed in Mac OS X as a percentage in either top or Activity Monitor, is a great number. It's easy to measure, easy to spot, and even the non-technophile can look at it and grok what that number is saying. There's a problem though...

CPU utilization is not a great metric, in fact, it's barely a metric at all, and Adobe's pushing of it is asinine. First, a high CPU utilization is, in and of itself, not bad. If you're doing something that is CPU-intensive, such as iDVD crunching video, then it is perfectly acceptable and logical to have high CPU utilization. The idea that "zero" is the only perfect number is the implication of Adobe's attempts to blame Apple, and it is, again, asinine. High CPU utilization is only bad if it is causing other processes to not run correctly, or, once a process no longer needs that much CPU, it still keeps eating it. There's a technical term for this, and it's called "That process has lost its damned mind, we'll have to kill that sucker daid."

The problem with the Flash plugin is not high CPU utilization. It's that the demented little fucker spinlocks your browser, even while using almost unmeasurable CPU. Just recently, I'm on the Huffington Post, and of course, the browser spinlocks. It's, of course, waiting for a Flash ad to load. Safari's CPU load? Well, it's locked, so hard to tell, but last reading was zero. The Flash plugin process? Zero to four percent.

Yet there's Safari, locked up all to hell.

So no, it's not all about CPU. It's all about a plugin doing stupid shit.

Dear Adobe: Stop Crashing My Browser.

Categories:     Adobe
Posted by John C. Welch at 22:41


Comments ()

January 29, 2010

Stop Crashing My Browser

So it occurs to me that the Flash Team is really, really mystified as to why people hate flash.

They think it's some kind of "Oh, it's cool to hate Flash" or "It's just iPhone fanbois" thing. This is of course wrong, and somewhat mystifying. However, when someone doesn't get the point, there is an extremely effective technique to fix that. State your point clearly, concisely, and repeat it like you were driving a nail into a board.

So here is the point we need to drive home to the Flash team:

"Stop Crashing My Browser"

They have to make that plugin not crash browsers. The problem isn't with Flash content, because as a few experiments show that flash files running in a browser, yet outside of that plugin don't kill your browser. It's not the content. The problem is the plugin crashing our browsers.

Stop Crashing My Browser.

The problem isn't with overuse or abuse of Flash. While Adobe encourages this, at the end of the day, the ultimate arbiters in whether to use Flash or not are the people building and running the web site, not Adobe. Getting into weedy discussions about Flasturbation may be fun, but that's not the problem. The problem is the plugin crashing our browsers.

Stop Crashing My Browser.

Those caring deeply about open standards and Adobe trying to turn the web into a walled garden by publishing the spec, but owning the playback mechanism are not wrong, but that is a different issue. This issue is simple and clearly focused.

Stop Crashing My Browser.

It's about stopping Flash from being one of, if not the leading cause(s) of browser crash reports for every company or group making a browser.

Stop Crashing My Browser.

It is about a Flash-heavy site being, effectively, a rather efficient DOS attack against every browser out there.

Stop Crashing My Browser.

It is about a plugin that does so much harm to just viewing a web page, that Google had to come up with an entirely new architecture to run plugins so that "plugins" wouldn't crash your browser. We know what they meant by that. They meant Flash. Even the Acrobat PDF plugin doesn't lock up your browser like Flash does.

Stop Crashing My Browser.

It is about letting the "Fingers In Our Ears Gang" that is the Adobe Flash Team, including their head muppet, John Dowdell know that they can list off Exciting New Features until the end of time, but no one cares. We care about one thing:

Stop Crashing My Browser.

If someone else from Adobe, such as John Nack posts some asinine defense of Flash, the only comment they should see is:

"We don't care. Stop Crashing My Browser."

Oh, and we include in this uber-model dialogs that lock up your browser to warn you about scripts running slowly. That is not something the user ever needs to care about.

So it's simple. If you have direct contact with the Flash Team, regularly or not, send them this message, every time you talk to them:

Stop Crashing My Browser.

If you communicate them via Email, Twitter, or Comments on a web page, send them this message over and over, (use some fucking sense. Don't be an ass and mail/twit-bomb them):

Stop Crashing My Browser.

Don't mess with non-Adobe Flash people. This is not their fault, they don't make the plugin. Adobe does, so Adobe can just fix it, or put up with

Stop Crashing My Browser.

Until they want to defenestrate themselves to make it stop. If you have a paper fetish, and lots of stamp money, send them post cards with

Stop Crashing My Browser.

written on it, "Stop Crashing My Browser." origami, whatever, (If you're rich, talented and have a lot of time and money to waste, what the hell, send them a nice metal sculpture that reads "Stop Crashing My Browser.") Be polite, but be insistent, (I can handle the rude folks, I'm good at it enough for everyone.) If someone from the Flash team tries to talk about exciting new features, politely cut them off and inform them that such discussions are tabled until such time as Flash Stops Crashing My Browser.

If you are in IT, and can do so without causing your users real problems, (their needs come first), make ClickToFlash, or whatever is appropriate for Windows & Linux a part of your standard machine image, and then email the Flash team to let them see the actual numbers, so it's harder for them to dismiss this. If you are not on a Mac and hate that plugin, then let the Flash team know that, so they can't dismiss it as a MacMac thing. If you are considering moving your RIA development to Silverlight, and complaints from your users/customers about Flash crashing their browsers have anything to do with that decision, let the Flash team know this, so they can see their delay in this area is going to cause them real actual problems.

Stop Crashing My Browser.

When Flash does stop crashing our browsers, or we don't need that plugin any more, then we can care about anything else the Flash team has to say. But until then:

Stop Crashing My Browser.

Stop Crashing My Browser.

Stop Crashing My Browser.

It's the only message they need to hear.

Categories:     Adobe
Posted by John C. Welch at 08:54


Comments ()

January 27, 2010

More Flash crying from Adobe

John Nack has a bit up about Flash. Go read it, it's certainly better than the companion piece our ever-vigilant Flash fluffer Dowdell put up. but it's still standard Adobe whining about the eeeebul Flash haters.

John has a habit of basing most of his arguments around popularity or what other kids are doing wrong and how that makes it all okay, and this is no exception. The notable bits:

Let's be clear: It's fine to say that Flash is flawed; it is. (You know who'd agree? The Flash team.)

Bullshit. Show me the blog post where the head, or anyone, from the Flash team got up and apologized for not being able, to date, make a plugin that didn't fuck up your browser, make it more crash- or lockup-prone, and generally eat shit alive. Where is this public admission? What they say in private doesn't really count.

But let's also be honest and say that Flash is the reason we all have fast, reliable, ubiquitous online video today. It's the reason that YouTube took off & video consumption exploded four years ago. It's the reason we have Hulu, Vimeo, and all the rest--and the reason that people now watch billions of videos per day (and nearly 10 hours apiece per month) online. Without it, we'd all still be bumbling along.

That's true, and completely beside the point that people who hate Flash are making. The popularity of Flash does not change the fact that as it is currently designed, the Flash plugin makes the web interactive, and suck. It doesn't matter how cool the fucking site is, if my browser spinlocks waiting for yet another fucking Flash dialog about a script making my computer run slow. It is not a speed issue, it is not a feature issue.

The problem with Flash is that it makes your browser less stable than a drunken sailor with an inner ear infection on a pogo stick in a mud slide.

If Flash didn't lock up my browser, crash my browser, and all the rest, I'd have far fewer problems with it. But since it fucks up my browsing humpty times every goddamned day, it is on my shitlist. Change the stability facts of Flash for the better, and I'll change the opinion based on those facts. Barring that, stop pulling my leg, it's getting sore.

Macromedia was the only company that delivered truly ubiquitous (99% penetration) video playback. Apple didn't*. Microsoft didn't. Real didn't. (Remember how you used to see sites offering multiple streams, making the user pick a player, because the content creator couldn't rely on everyone being able to view one format? Good riddance to that crappy customer experience.) Content creators, whose income is proportional to their ability to reach customer eyeballs easily and reliably, have voted with their feet, moving to Flash. As a result, more than three quarters of online video now streams in Flash formats (up from 25% three years ago).

All true, and a complete strawman. People hate Flash because it fucks up their browser's stability. Get used to me saying that, because a) it's the fucking truth, and b) see a).

All these years later, we still don't have a standard, browser-native alternative, much less one that's achieved widespread viewership. (WebKit-based browsers remain in the single digits on the desktop. Firefox, which uses a different video format, is at 25%. Microsoft is off doing its own thing.) That sucks.

What sucks is that the most ubiquitous, widely-used plugin on the Internet is also one of the, if not the leading causes behind browser lockups and crashes. (for more data, see Gruber's latest article on this.) What sucks is that all the Flash horses and all the Flash men, can't make that fucking plugin not crash my browser again.

and again.

and again.

I don't doubt that some video standard will eventually emerge & make its way into most if not all browsers. In the meantime, Adobe spends millions of dollars a year building & giving away software--for which content viewers & even content creators don't have to pay a dime--to compensate for the rest of the world's failure to get the job done.

If I give you a gold star, will you give me a plugin that doesn't fuck up my browser, almost continuously?

Maybe that sounds harsh, but I find the Flash-bashing tedious and hollow. Flash has all kinds of shortcomings; helping address them is why I joined Adobe, for God's sake! But Flash, as Winston Churchill might say, is "the worst except for all the others ever tried." It will improve, as will competing implementations. In the meantime, how about we give the devil (if that's how you see it) his due, giving Flash credit for helping the world get this far?

I should give you credit for fucking up the stability of my browser, and creating one of the most fertile attack vectors since ActiveX?

How about "No". Does "No" work for you? Again, give us a stable plugin, and maybe hire a fucking security wonk for that team with some actual power, and then we'll talk about credit. If you want the Flash-bashing to stop, STOP FUCKING UP BROWSER STABILITY MORE THAN ANY OTHER SINGLE CAUSE.

Stepping a bit beyond video, I'm personally delighted to see Web standards like HTML5 emerge. Adobe makes nearly all its money selling authoring tools that target great runtimes. (Conversely, as I've mentioned, Adobe loses money building runtimes (Flash Player, Adobe Reader) that it gives away in order to sell authoring tools.) More great runtimes to target means more opportunities to build content for them. Adobe will naturally follow the money, building authoring tools that produce what customers demand, and that includes HTML5-based work. Don't believe me? Check out a demo of Illustrator, Flash, and Dreamweaver targeting the HTML Canvas tag.

You know what the best part of that was? The part where because I'm not using the Flash plugin to play that video, my browser was responsive and stable, and not eating my machine alive. Also, spare us the holystoning bullshit about how much money you lose. You make it up in selling Flash authoring environments, Flex 3 builder, LiveCycle, Flash Interactive Media Server, Flash Media Streaming Server, Flash Access, etc. Adobe ain't losing shit for money on Flash, and shame on you for thinking we're too stupid to see that.

Guess what, though? When I posted that story, almost no one paid attention. People want a certain "killer" narrative: Good guys vs. bad guys, open vs. proprietary, blah blah. That's simpleminded and lame.

Or maybe their browsers kept locking up and they stopped trying to watch it. I guess crashing browsers isn't lame, but complaining about Flash crashing browsers is lame. What a fun reality you live in!

I keep seeing the video standards discussion phrased as "H.264 vs. Flash video**" (e.g. John Gruber writing about Apple "replacing" Flash video with H.264). Apparently people are unaware that Flash has been playing H.264 for years. It's easily the most popular H.264 player in the world.

And the crashiest! and the most resource intensive! See, I can do this too! Also, when did Adobe do this? 2007. What else happened in 2007 that might spur Adobe to do this. Hmm...what announcements from Apple and YouTube that year might have pushed Adobe to support that standard in Flash. G. Eye Wunder.

You know what else I see when i play the same h.264 Video back in a different player? less slow, less crashy, less lockup. Good thing Flash is so excellent. That is when it's NOT CRASHING MY BROWSER.

Adobe's choice to embrace H.264 in Flash is what allows sites like Vimeo and YouTube to create HTML5/AVC (i.e., non-Flash) versions of their sites without gobbling up petabytes of storage and loads of CPU cycles creating and storing alternate versions of their videos. Instead of locking people into some proprietary solution it created, Adobe has spent millions of dollars to enable use of a more standard format.

First: AAAAAHAHAHAHAHAHAAHAHAHAHAHA. Adobe pointing the "locking people into a proprietary solution" finger. You're a funny guy Nack.

Second: Forcing playback of an open standard through an Adobe-controlled plugin is not what you want us to think it is, nor are we in fact that stupid.

It doesn't require you to target multiple runtimes (browsers, etc.) from multiple vendors. Instead, there's effectively one Flash Player with a predictable set of capabilities. Fonts, pixels, etc. render consistently across OSes, browsers, and devices. You don't need something like BrowserLab (a free Adobe service, by the way) for Flash.

All the while fucking up accessibility, radical things like selecting text, increasing your font size to read text easier, forcing everyone to rely on a single proprietary plugin and CRASHING MY FUCKING BROWSER. Every time Flash might give me a hardon, that crash thing kills it faster than Phyllis Diller's tits.

If Adobe develops a new technology (e.g. the Text Layout Framework, leveraging InDesign tech and enabling beautiful Web typography), it can be deployed quickly & reliably to all systems. That is, we don't have to say, "Yeah, we'd love to see better type on the Web, but first we have to convince these groups to add support, and then wait several years for updates to achieve broad adoption, and then hope it all works the same..." We can just do it, and support will hit critical mass quickly.

It will be beautiful right up until the Flash plugin crashes my fucking browser again. Then it will be a thing of hatred.

In a sense it's a more Apple-like approach: Control things yourself, so design-by-committee doesn't compromise your product. Open-sourcing Flash would lead to a fragmentation of the format & Flash runtimes, and that would destroy the predictability and agility that differentiate Flash from other standards.

It would also mean the Flash team might have some fucking competition other than Silverlight, and you can't have that. Why, THEIR plugin might not CRASH MY FUCKING BROWSER THE WAY ADOBE'S DOES.

And now the money shot to show that no one at Adobe understands the fucking problem, at all.

-- On Mac vs. Windows performance --

Finally, let's turn to a touchy subject.

If Flash runs faster on Windows than on Mac, that must be proof of Adobe's incompetence and/or anti-Mac malice, right? Of course, if Flash ran faster on Mac than on Windows, that would be taken as proof of OS X's modern awesomeness. Heads they win, tails we lose. (Come on, tell me I'm wrong.)

Despite the Flash Player team investing disproportionate resources in the Mac player (where the Mac has ~5% market share to 90+% for Windows), and despite them making big strides on the Mac, it's true that Flash performance on OS X has lagged behind Flash on Windows. That needs to change.

My understanding is that there's work that both Adobe & Apple could do to improve matters. Mac users*** complain about high CPU usage when playing video. The latest Flash Player uses many fewer CPU cycles for video, but the needed hardware decoding support isn't available on the Mac right now. I don't have any inside info here, but I've heard that the Safari team is a great group of folks, and I hope they're able to work with the Flash Player team to added the desired support.

It is not about speed you fucking nimrod. It is really not even about CPU cycles. It is about Stability. Stability, stability, stability. Let me explain. Right now, I don't ever, ever think that Flash doesn't play videos fast enough. Youtube can't seem to download shit fast enough ever since they started pushing HD, but that's a bandwidth, not a Flash issue. If the content is there, the performance of said content seems to work just fine.

It is also not about CPU cycles in and of themselves. If the Flash plugin is doing something hard, or intensive that requires CPU, then it should use it. That isn't a problem. It is when Flash needs 100% of a CPU to play a fucking 4" x 3" movie in my browser, even after I wait for the download, and QuickTime needs a quarter of that to play goddamned HD that we get a little annoyed. Also, Apple isn't Microsoft. If you're expecting Apple to allow you to diddle the hardware directly, then I hope Jobs personally takes a leak on your desk as he says no, no, a thousand times no. This is not DOS, you keep your fucking hands off the hardware.

The problem, the big problem, the one we want fixed more than imagined speed problems, or CPU usage that isn't inherently bad is this:

STOP CRASHING OUR FUCKING BROWSERS

STOP CRASHING OUR FUCKING BROWSERS

STOP CRASHING OUR FUCKING BROWSERS

-- In Conclusion --

Stop crashing my browser

I'm very optimistic about Flash, Web standards, and what Adobe can to help customers. In particular:

You'll stop crashing my browser.

The Flash Player team has been very hard at work leveraging the GPU to deliver great performance on mobile devices. I expect those optimizations to make their way into the desktop Flash Player.

I hope the "stops crashing the fucking browser" fixes make their way into the Flash player.

Developers are pushing standards like CSS 3D, WebGL, and Canvas to deliver interesting results. It's about time Web browsers got good at this stuff, for everyone's sake, and those enhancements roll right into Adobe AIR and the Creative Suite (both of which use WebKit). Adobe sells tools that can adapt to fit customers' needs. As new technologies open new possibilities, Adobe will deliver great authoring apps.

If they don't stop crashing my fucking browser, I could give a fuck less what Adobe is going to do.

There. Clear enough even for Nack.

Not for Dowdell though. That motherfucker is in some kind of VM.

Categories:     Adobe
Posted by John C. Welch at 01:09


Comments ()

January 23, 2010

Appending files!

When we left off last time, we had finished up with creating a brand new file, grabbing data for it, formatting the data into a line of tab-delimited text, and then writing that to the file.

Now, we're going to append data to an existing file, but first, I made some changes to the creating a new file code. Since this is tab-delimited text, it's reasonable to assume that it's going to get used in a spreadsheet application of some kind. Well, you'd be assuming, not me, since that's why I wrote it that way. However, I realized that header rows don't suck, so I created one of those in the new file, so that when you open it in something like Numbers or Excel, you get a nice header row that explains what each row does. I'm not going to put up the entire function again, just the new lines:

     set theTempString to "Time" & " " & "SSID" & " " & "Authmode" & " " & "Channel" &

" " & "Data Rate (Mbps)" & " " & "Signal Strength (dbm)" & " " & "Signal Noise (dbm)" & " " & "MAC of WAP" & "

"
     --createt the header line for the file

     set theFileString to my NSString's stringWithString_(theTempString)

     --AppleScript string to NSString

     set theFileData to theFileString's dataUsingEncoding_(NSUTF8StringEncoding of current application)

     --NSString to NSData

     theFileHandle's writeData_(theFileData)

     --Write the header to the new file

     set theFileLengthPointer to theFileHandle's seekToEndOfFile()

     --move the file pointer to the end of this string

     --since we KNOW what created this line, we don't have to be paranoid, like we are

     --with appending data

The first few lines are nothing major. We create a string with the column headers ended by a return, and shove that into an NSString. (We could save a line of code and just put the raw string in the stringWithString_() method, but this way is neater to read.

The new stuff is the set theFileLengthPointer to theFileHandle's seekToEndOfFile() line. What this does is move the "start writing data here" pointer to the end of the file, so that the next time we write data to that file, it's going to start in the right place. We use this in the appending function too:

on appendToExistingFile_(sender)

     set theOpenPanel to my NSOpenPanel's openPanel()

     --create an open panel

     theOpenPanel's setCanChooseFiles_(1)
     --the user can choose files
     theOpenPanel's setResolvesAliases_(1)
     --it will resolve aliases to their original file
     theOpenPanel's setCanChooseDirectories_(0)
     --the user cannot chose a directory, it makes no sense in this application's context
     theOpenPanel's setAllowsMultipleSelection_(0)
     --the user cannot choose multiple files, again, not appropriate for the application's context
     set theOpenPanelResult to theOpenPanel's runModal()
     --run the Open panel
     if theOpenPanelResult is 1 then --if the user clicked okay
          my appendToExistingDataButton's setEnabled_(false)
          --disable the button to append to an existing data file
          --we only work on one file at a time.
          my createNewDataButton's setEnabled_(false)
          --disable the button to create a new data file
          --we're appending to a file, letting the user create a new one
          --while this is going on would be dumb
          set theDataFilePath to first item of theOpenPanel's filenames()
          --get the file paths and names, in case we need them
          --this returns an array with a single item, so we use that
          set theDataFileURL to the first item of theOpenPanel's URLs()
          --get the URL to the file, as that's what we should be using
          --since this returns an array/list, we have to get the first item of that list
          set theFileHandle to my NSFileHandle's fileHandleForWritingToURL_error_(theDataFileURL, missing value)
          --create the file handle
          set theFileLengthPointer to theFileHandle's seekToEndOfFile()
          --set the file handle pointer to the current end of the file
          set theFileString to my NSString's stringWithString_("
")
          --we're going to write a single blank line just to be sure we aren't jamming on the end of the data.
          --We don't know for sure what created this original file, so better an extra blank line than mangled data.
          set theFileData to theFileString's dataUsingEncoding_(NSUTF8StringEncoding of current application)
          theFileHandle's writeData_(theFileData)
          set theFileLengthPointer to theFileHandle's seekToEndOfFile()
          --seek to new end of file
          set createDataFile to false
          --make sure this flag is false
          set appendToExisting to true
          --we need this to be true
          end if
end appendToExistingFile_

This code really works like the new file code with a few exceptions. Obviously we're not using the save panel, since the file already exists, so for this, we use NSOpenPanel instead. We set a few options, like allowing the user to choose files, resolving aliases if one is chosen, and disabling multiple files, and choosing directories, as neither of those make sense in this case. Once that's done, we use the open panel's runModal() to display the panel. If the user picks a file and hits okay, then we disable append and create new file buttons in the App UI, since we don't want another file being used while we're writing to this one.

Like we did with the save panel, we grab both the path and the URL, although unlike the new file code, we don't actually need the path. Also, unlike the save panel, NSOpenPanel returns us an array, or AppleScript list, not a text string. Since we need the text string, we set the file path and url variables to the first item of the array (Since we only allow the user to pick a single item, this assumption is fairly safe.):

set theDataFilePath to first item of theOpenPanel's filenames()
set theDataFileURL to the first item of theOpenPanel's URLs()

Next, we get the file handle for the file at the end of the URL via fileHandleForWritingToURL(), which gives us a file handle for the file we're going to be appending data to. Since we aren't dealing with error handling, we just pass nil or missing value to the method for the error parameter.

Now, we're appending data to the end of an existing file, so we want to make sure we're starting at the end of the file. Like we did in the new code for the new file code, we use seekToEndOfFile() to make sure we're starting at the end of the file. (Yes, as someone used to being far more explicit with such things in AppleScript, the 'trust me, the pointer's in the right place' thing going on here was a little disconcerting, but it obviously works, so...) Next, just because I want to be REALLY SURE that we can clearly tell where the data we're appending starts, (I'm paranoid, sue me), we write a single return to the file, then call seekToEndOfFile() again. So now we're at the end of a file, and we should have a blank line between the new and the old data.

We also make sure the createDataFile flag is false, and the appendToExisting flag is true. Once that's taken care of, we're done with this function, and we move on to the last of the new code for appending files in the loadData() handler.

This code is, as you can see, pretty much like the new file code:

if (appendToExisting is true) and (theSaveFileFlag is true) then
--both have to be true

     set theTempString to (theTime's stringValue() as text) & " " & (currentSSID's stringValue() as text) &

     " " & (authMode's stringValue() as text) & " " & (currentChannel's stringValue() as text) &

     " " & (currentDataRate's stringValue() as text) & " " & (signalStrength's stringValue() as text) &

     " " & (signalNoise's stringValue() as text) & " " & (currentWAPMAC's stringValue() as text) & "

"
     set theFileString to my NSString's stringWithString_(theTempString)

     --convert the AppleScript string to NSString

     set theFileData to theFileString's dataUsingEncoding_(NSUTF8StringEncoding of current application)

     --NSString to NSData

     theFileHandle's writeData_(theFileData)

     --write us some data

end if

The cleanup code when you're done writing is the same code as the new file code, so we don't have to go over it again.

The next update may be a while, I'm trying to dig up a way to do the signal vs. noise charts that will display directly in the application.

Categories:     AppleScriptObjC, Applescript
Posted by John C. Welch at 22:08


Comments ()

January 21, 2010

The Write Way...

(Just for those who care: no, i'm not an experienced Cocoa or ObjC programmer. It should have been obvious if you knew enough to ask, but just to clear it up.)

This post, we're going to take the initial save file setup we had from last week, and expand that to actually writing data to a new file. Doing this is going to require splitting up the overall writing code across three handlers in my application. In this case, we have made some minor changes to the code that executes after the user clicks "Okay" in the save panel dialog:

if theSavePanelResult is 1 then

     --if they clicked the 'save' button, then we want to get the path and set some flags
     set theDataFileURL to theSavePanel's |URL|()
     --get the encoded URL, which is not the file path, but we'll need it
     set theDataFilePath to theSavePanel's filename()
     --get the posix path to the file
     set createDataFile to true
     --we're creating a new data file, so this has to be true
     set appendToExisting to false
     --we are not appending data, so set to false
     set theFileManager to my NSFileManager's defaultManager()
     --create a file manager object so that we can create a blank file
     set theCreateFileResults to theFileManager's createFileAtPath_contents_attributes_(theDataFilePath, missing value, missing value)
     --creates a blank file at the path specified. Do NOT use "my theFileManager's..." because errors will happen
     set theFileHandle to my NSFileHandle's fileHandleForWritingToURL_error_(theDataFileURL, missing value)
     --use this to write to the file URL

end if

So as you look at it, you can see that all we did was make the file handle setup the last line. Note that I've set up theFileHandle as a property, because it's the lazy way to avoid scope issues. So we clicked our button to create a new file, we gave it a name, and a location, and we have a file path to get data into it. How do we build the data? Well, first, we have to define what that data is. In this application's case, it's series of lines of tab-delimited text. Each item is a bit of info about the current WiFi network, along with a timestamp for when the data was read. This can be entered as part of the track functionality, writing one line of data per second, or when you hit the refresh button, to write the data manually. The file handle is closed when you either turn off the track function, or deselect the save to file checkbox in the application UI. So let's look at building the data string.

Since I have a nice handler to grab the wifi data and populate the UI with the manual refresh or with the track button, we'll add some code to that. Here's the entire loadData handler:

on loadData(theCurrentInterface)

     currentSSID's setStringValue_(theCurrentInterface's ssid())
     --set the contents of SSID field to the current SSID
     set theCurrentAuthMode to (theCurrentInterface's securityMode() as text)
     --it's an NSNumber, will deal with it later. Forcing to text works for now
     if theCurrentAuthMode is "0" then
     --mode number to mode name

          authMode's setStringValue_("Open")

     else if theCurrentAuthMode is "1" then

          authMode's setStringValue_("WEP")

     else if theCurrentAuthMode is "2" then

          authMode's setStringValue_("WPA1 Personal")

     else if theCurrentAuthMode is "3" then

          authMode's setStringValue_("WPA2 Personal")

     else if theCurrentAuthMode is "4" then

          authMode's setStringValue_("WPA1 Enterprise")

     else if theCurrentAuthMode is "5" then

          authMode's setStringValue_("WPA2 Enterprise")

     else if theCurrentAuthMode is "6" then

          authMode's setStringValue_("WiFi Protected Setup")

     else if theCurrentAuthMode is "7" then

          authMode's setStringValue_("Dynamic Wep 802.1X")

     else

          authMode's setStringValue_("Unknown/Invalid")

     end if

     currentChannel's setStringValue_(theCurrentInterface's channel())
     --set the channel
     currentDataRate's setStringValue_(theCurrentInterface's txRate())
     --set the data rate in Mbps
     signalStrength's setStringValue_(theCurrentInterface's rssi())
     --set the signal strength in dbm
     signalNoise's setStringValue_(theCurrentInterface's noise())
     --set the signal noise in dbm
     currentWAPMAC's setStringValue_(theCurrentInterface's bssid())
     --set the MAC of the base station
     theTime's setStringValue_((time string of (current date)))
     --let's add the code to save to a new file
     if (createDataFile is true) and (theSaveFileFlag is true) then

          set theTempString to (theTime's stringValue() as text) & " "
          & (currentSSID's stringValue() as text) & " "
          & (authMode's stringValue() as text) & " "
          & (currentChannel's stringValue() as text) & " "
          & (currentDataRate's stringValue() as text) & " "
          & (signalStrength's stringValue() as text) & " "
          & (signalNoise's stringValue() as text) & " "
          & (currentWAPMAC's stringValue() as text) & "
"
          set theFileString to my NSString's stringWithString_(theTempString)
          set theFileData to theFileString's dataUsingEncoding_(NSUTF8StringEncoding of current application)
          theFileHandle's writeData_(theFileData)
          --you could probably also use fileHandleForWritingToPath here, but since URLs are the way of the future
          --we should use them where we can
          --we stash the close function where it's going to be actually used

     end if

end loadData

There's really not much going on here. This handler, (and you can tell it's a 'normal' AppleScript handler because it doesn't have an underscore in the name), is passed a WiFi interface object, theCurrentInterface. It then pulls data from that to set various text fields in the Application UI. For example, we grab the SSID of the current WiFi network via theCurrentInterface's ssid(), and use that to set the contents of that text field in the UI via currentSSID's setStringValue_(theCurrentInterface's ssid())

Since the enumeration for the authentication mode can be one of 9 values, including "other", we have a series of if then else statements to handle that. To get the time string we use for theTime, instead of using the Cocoa technique, and NSDate/NSDateComponents, we use a more traditional AppleScript way: time String of (current date). It works just as well as any other method for our needs, and is simpler to code. adding that to the UI is then a single line: theTime's setStringValue_((time string of (current date)))

So now, we have all the information we need to build our string that we want to write to file. To do that, we again, combine Cocoa and traditional AppleScript to build a tab-delimited line with a trailing return:

set theTempString to (theTime's stringValue() as text) & " " & (currentSSID's stringValue() as text) & " " & (authMode's stringValue() as text) & " " & (currentChannel's stringValue() as text) & " " & (currentDataRate's stringValue() as text) & " " & (signalStrength's stringValue() as text) & " " & (signalNoise's stringValue() as text) & " " & (currentWAPMAC's stringValue() as text) & "
"

The blank spaces in between the quotes are the presentation of the tab formatter, \t. The odd quotation mark by itself is the presentation of the return formatter, \r.

So now we spend three statements on converting theTempString to an NSData object and writing that to a file. First, we create the NSString object: set theFileString to my NSString's stringWithString_(theTempString). Pretty clear, we use the stringWithString function, passing it the temp string. Next, we encode theFileString and convert it to an NSData Object:

set theFileData to theFileString's dataUsingEncoding_(NSUTF8StringEncoding of current application)

We're using UTF8 encoding, because that's the better way to do things, but there are a variety of encodings available, including MacRoman, etc. One thing to watch here is that you have to set the encoding as "<encoding method> of current application", or it fails miserably. Finally, we write that NSData object to the file we created via the file handler:

theFileHandle's writeData_(theFileData)

Every time this handler is called, as long as createFileData and theSaveFileFlag are both true, we get a line of data written.

So what happens when we're done writing? Well, it wouldn't make a lot of sense to put that code here, so we put it in the functions that we use when we're done tracking data, or when we deselect the save file checkbox. First, when we stop tracking:

on timerFired_(thetimer) --this handler runs the actual code for the timer

     if trackButtonSTate is 1 then --'on'

          loadData(theCurrentInterface) of me --grab stats once per second

     else if trackButtonSTate is 0 then --"off"

          thetimer's invalidate() --kill the timer
          theFileHandle's closeFile() --close the file handle we've been writing to
          set theSaveFileFlag to false --kill the save file flag
          set createDataFile to false --kill the new file flag
          set appendToExisting to false --kill the append file flag
          saveToFileCheckBox's setIntValue_(0) --disable the checkbox
          my createNewDataButton's setEnabled_(false) --disable the button to create a new data file
          my appendToExistingDataButton's setEnabled_(false) --disable the button to append to an existing data file

     end if

end timerFired_

We put the file handle cleanup code in the same block as the timer cleanup code. When you disable tracking, it's going to run the code to kill the timer that's controlling how fast the tracking is happening anyway, so it makes sense to put it here. It's only one line to shut down the file handle:

theFileHandle's closeFile()

That's it, the file handle is closed, file writing is done. The rest is just cleaning up properties and resetting controls to give a better indication to the user that they're no longer recording data to a file.

The same basic thing happens if they disable the save to file checkbox:

on saveToFile_(sender)
--when you click the "Save to file" checkbox this ONLY controls the button states, not what the buttons do

     if sender's intValue() is 1 then --if you're checking the checkbox

          set theSaveFileFlag to true
          my createNewDataButton's setEnabled_(true) --enable the button to create a new data file
          --the "my" is critical to having the now enabled button be able to send events
          --without "my", the button knows it's enabled, nothing else does
          my appendToExistingDataButton's setEnabled_(true) --enable the button to append to an existing data file

     else if sender's intValue() is 0 then --if you're de-checking the checkbox

          theFileHandle's closeFile() --close the file handle we've been writing to
          set theSaveFileFlag to false --kill this file flag
          set createDataFile to false --kill the new file flag
          set appendToExisting to false --kill the append file flag
          my createNewDataButton's setEnabled_(false) --disable the button to create a new data file
          my appendToExistingDataButton's setEnabled_(false) --disable the button to append to an existing data file

     end if

end saveToFile_

Nothing new here, close the file handle, reset things, and bob's your uncle.

Once you wrap your head around things, especially the encoding method thing, writing data to a file is pretty easy. Really, that was the biggest frustration for me, because even reading the Cocoa docs on this, there was nothing to really indicate that had to happen. Again, many thanks to Shane, Craig, and everyone else on the AppleScriptObjC list for all their help.

Categories:     AppleScriptObjC, Applescript
Posted by John C. Welch at 17:17


Comments ()

January 19, 2010

Acrobat Post, or "It's been a while..."

...Since I really got my rage on.

Warning....this shit's going to get ugly and fast. If you don't like profanity, you might want to leave the room. Unca John's in a motherfuckin' mood.

As we all know, Adobe released yet another patch for yet another fucking Javascript hole in their fucking PDF scripting implementation. (Is anyone surprised by this anymore? Acrobat is the new IE in this respect.)

So, Adobe releases the updates, and I start planning the update. Because I knew it would suck. Why? Because it's fucking Acrobat, and that team has their heads up their collective asses, and the idea of making applying critical security patches easy? HA! SCREW YOU HIPPIE!

Sigh. So I know this will suck because thanks to some idiotic decision made by some idiot, you can't run updates unless you're logged in as an administrator. Now, let me be clear about this:

I am not saying you have to authenticate as an administrator to run/apply the updates. That would be normal and sensible.

I am saying that you cannot even run the Adobe Updater application to check for updates unless you are logged in as an administrator. The "Check for updates" option doesn't even show up in the fucking help menu unless you're logged in as a fucking administrator.

As well, at least on the Mac, do you know how you remotely deploy Acrobat updates?

You rebuild the fucking installer. HA! SCREW YOU HIPPIE! That's right, even now, even fucking now, the gods-damned Acrobat updater is still not a fucking native install package. So you either spend a lot of money on third party tools to do the Acrobat team's job for them, you start snapshotting machines to create an installer, or you do it fucking manually.

Even then, even when you do it manually, the goddamned chickenfuckers on the Acrobat team make sure you feel every goddamned hangnail on that fist as it goes up your ass. Installed CS 3 or 4 with Acrobat Pro? Ah, you're probably stupid enough to just run the updater application.

HA! SCREW YOU HIPPIE!

It will update every.component.in.CS, but it will never, never update Acrobat. Because it doesn't know Acrobat is there. You may have installed Acrobat with CS, but just like a shitty reflection of Adobe's corporate structure, the only way to get the CS updater to know about Acrobat is to start Acrobat, then run the fucking updater from within Acrobat. After that, only after that will the updater know you installed a fucking application that was RIGHT FUCKING THERE IN THE INSTALLER YOU RAN INITIALLY. HOLY SHIT, WHAT KIND OF DEMENTED FUCKWIT THOUGHT THIS UP? WHAT KIND OF MENTALITY THINKS THAT THIS KIND OF VIRTUAL FUCKING CLEVELAND STEAMER OF AN UPDATE PROCESS IS GOOD? WHAT KIND OF DISEASED COPROPHILIAC DO YOU HAVE TO BE TO PUT PEOPLE THROUGH THIS?

...we're not done yet.

Like a lot of people, we have to keep CS3 and CS4 around for a little longer, for a variety of reasons. We're working on fixing that, but it's how things are for now. This means a lot of our machines have both Acrobat 8 and 9 on them. Yes, the thought of updating both is like getting sounded by an epileptic bodybuilder with bad gas in the middle of a grand mal.

Acrobat 9 first. Huh, look at that. When I run the updater, (after figuring out that I had to start Acrobat first), I see what is, for Adobe, a fucking miracle. An honest to god fucking miracle. It's a combo update. It's huge mind you, but you go from Acrobat Pro 9.0 to 9.3 in one shining update. I almost weep for joy. This is cool, I'll just go download it from Adobe's site, and then I can speed up the overall update time. Clickety-click I go...

HA! SCREW YOU HIPPIE!

You can't download a 9.3 cumulative update from Adobe. The 9.3 updater you download there can only update 9.2

The 9.2 updater can only update 9.1.3

The 9.1.3 updater can only update 9.1.2

The 9.1.2 updater can only update 9.1.1

The 9.1.1 updater can only update 9.1

The 9.1 updater updates 9.0

So if you want to do it outside of the Adobe updater in Acrobat, and you're going from 9.0 (which is not surprising, considering how insanely fucking tedious and stupid the Acrobat team has made this fucking process, because it never fucking occurs to them that having the application of patches be easy is A FUCKING GOOD THING), you have to download, and apply, in order, SIX PATCHES. MANUALLY!

So the only way to get WAIT, HOLD ON, HOLD FUCKING ON!

I was wrong. There is no cumulative update for Acrobat 9. I can't even begin to see how I could have been so stupid as to think that the Acrobat team would even try to make their own lives easier. When you run that Acrobat 9.3 update, it downloads all the point updates as separate disk image files, then proceeds to, one at a time, mount and apply them.

AAAAAAAAHAHAHAAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHHAHAHAHAHAHAHAAHAHAHAHA

<INHALE>

AAHAAHAHAHAHAHAHAHAHAHAHAHAHAHHAHAAHHAAHAHAHAHAHAHAHAHAHAHAHAHAHHAHAHAHAAHA!

Just when I think there might be a scintilla of competence on the Acrobat team, they once again prove that if you replaced them all with diarrhetic lemurs, not one user of their product would know the fucking difference. What a complete fucking idiot collective.

Hold on, I think I soiled myself, I was laughing so hard. One sec.

Okay, so yeah...you run the !cumulative updater, and every time the next stage of this asinine dance starts, you get asked about 'repairing' the installation. Yes, you may have already set this, but the installer wouldn't want you to be able to do anything else while it's running, so it makes sure you have to pay constant fucking attention to it.

Of course, now that you've run it, next time, you only have to run the adobe updater, as it now knows that Acrobat is also on the machine.

So that was 9.0 to 9.3

8.0 to 8.2 is actually worse, because not only do you have every.single.problem with the CS3/Acrobat 8 updater that you did with the necrotic lump of shambling shit that Acrobat 9's update process is, but, Acrobat 8 doesn't even get the !cumulative update.

So you have to re-run the installer. Over. and Over. and Over again.

It is appalling, no, it is far, far beyond appalling how bad this process is. To apply a CRITICAL SECURITY UPDATE on a supported platform, you either have to re-write Acrobat's gangrenous discharge into something usable, or you have to kick people off their machines to sit there and watch, either at their desk or via ARD/VNC/ACTC/Whatever.

Of course, Adobe does have enterprise deployment tips...for Windows only. Because according to the Acrobat team, (and I've been told this by members of the Acrobat team in email, comments, and in person on multiple occasions), the only people using Acrobat on Macs are graphic designers, and so IT doesn't care about Acrobat on Macs, so why support it? IN fact, there is an Acrobat Enterprise Deployment page. Windows only, well except for the security administration guide. That mentions plists, well, it mentions the word "plist". You never actually see an example of a plist file in the entire thing, (404 fucking pages...how ironic in this case.) Why? Well, this bit of text on page 78 explains:

The tables and examples use the Windows registry. Most are applicable to Macintosh systems.

Woo-Fucking-Hoo.They can't even put a fucking screenshot of a plist configuration in this fucking document, that's how allergic the Acrobat team is to lifting a fucking finger for their Mac OS X-using customers. The attitude of the Acrobat team towards their customers is to wit: vomitous. Oh, and before I forget..

HAH! SCREW YOU HIPPIE!

(I know, I know, the hatred of Acrobat for the Mac is well-known, it shouldn't bother me. They've lied about why their Office integration sucks on the Mac for how long? Shouldn't bother me, but since it makes my life suck in so many little ways, it does bother me.)

The fact that a company who is, supposedly, the employer of smart people, and considered one of the fucking paragons of design, cannot, for any reason, create updates for CRITICAL SECURITY PROBLEMS THAT HAVE ACTIVE EXPLOITS IN THE WILD is almost negligent, and if I had the money and the lawyers, I would be seriously considering suing Adobe to recover the time, and therefore money, it's taken me to deal with the raging incompetence shown by the Acrobat team.

Not a single one of Adobe's Acrobat customers did anything wrong. We didn't create the security holes, Adobe did. We didn't write the patch, Adobe did. Yet at no point, and in no way, is the Adobe Acrobat team trying, on any level in even the most pathetically simple manner to make it easy on people to install this patch on as many machines as possible in as little time as possible, and barring that, they can't even bring themselves to fucking APOLOGIZE for inflicting this effluvia of incompetence on the people who pay hundreds, thousands, hundreds of thousands, or even millions of dollars to purchase Acrobat and its ilk.

The only, the only mistake we made was in thinking that maybe this time, the Acrobat team will do something right. That, dear readers, is our fault.

I hope, with all my heart and soul, that I am alive the day that Acrobat as a product team is killed by something, from any other company, or open source. Because I will take whatever money I have, and pay for a big fucking truck. On the side of that truck, in the biggest possible letters will be written:

"NO ACROBAT, FUCK YOU".

The fact that everyone on the Acrobat team isn't publicly and continuously apologizing for this debacle, the latest in a long line of Acrobat-driven debacles, is as shameful as anything else.


Oh...and I haven't even started updating Adobe Reader...

Categories:     Adobe
Posted by John C. Welch at 16:30


Comments ()

January 18, 2010

Adventures in creating new files to save

Before we get into today's bit:

  1. File URLs are not File Paths

  2. Just because NSFileHandle can deal with URLs, that doesn't mean NSFileManager does. So you can use either a path or a URL to create a handle to an existing file, but to create the file itself, you have to use the path

  3. Documentation that interchanges terms like "path" and "url" makes me want to go all stabbity-stabbity

  4. Knowing when NOT to use "my" is important too

Now, on to today's bit.

I've been spending most of my time of late dealing with the "Cocoa" way to save and open files. Now, there's no functional reason to do this, AppleScript has had some solid ways to do this for some time now in Standard Additions. So, if you want to create a new file, open a write handle to it, write some data to it, then close the file handle, you have:

set theFile to choose file name with prompt "enter a new file name to be created, or choose a new file that will be obliterated" default name "newfile.txt"
set theFileHandle to open for access theFile write permission true
write aBigBunchOfData to theFileHandle
close access theFileHandle

It's pretty straightforward. Choose File name lets you 'choose' a file that may not exist yet. We then use Open For Access with write permission to get a handle to the file that we can use for writing data. Write then shoves data into the file handle, and close access closes off the file handle. If you pick an existing file, any data in that file is obliterated. (We'll deal with appending data later.)

Doing this in AppleScriptObjC, at least the "Cocoa" way is a bit more involved, but worth digging into, as a teaching exercise alone.

So, here's the code block I have for creating a new file. Note that I'm not yet actually writing data into it, just getting everything ready to do so:

on createNewFile_(sender) --create a new file
     set theSavePanel to my NSSavePanel's savePanel() --create the save panel object
     theSavePanel's setMessage_("Create New Data File")
     theSavePanel's setAllowedFileTypes_(theFileTypeArray)
     --we want to be specific here, and only allow certain types, in our case, text
     theSavePanel's setExtensionHidden_(0)
     --in the AppleScriptObjC is not objectiveC file: for bools, use 1 or 0 not YES or NO
     --if you don't, you'll get fussed at and you'll never know why
     theSavePanel's setAllowsOtherFileTypes_(1)
     --yes we only want text, but we don't want to be dicks about it.
     --if someone really wants something else, sure.
     --if someone really wants to use .dat or whatever, fine, they can
     theSavePanel's |setNameFieldStringValue_|("WiFi Analyzer Data.txt")
     --i named this as STringValue once and AppleScript being, well
     --AppleScript, it will hang on to that forever! so the pipes around it help me deal with that
     set theSavePanelResult to theSavePanel's runModal()
     --display the panel and get the binary result
     if theSavePanelResult is 1 then
     --if they clicked the 'save' button, then we want to get the path and set some flags
          set theDataFileURL to theSavePanel's |URL|()
     --get the encoded URL, which is not the file path, but we'll need it
          set theDataFilePath to theSavePanel's filename()
          set createDataFile to true
     --we're creating a new data file, so this has to be true
          set appendToExisting to false
     --we are not appending data, so set to false
          set theFileManager to my NSFileManager's defaultManager()
     --create a file manager object so that we can create a blank file
          set theCreateFileResults to theFileManager's createFileAtPath_contents_attributes_(theDataFilePath, missing value, missing value)
          --creates a blank file at the path specified.
          --Do NOT use "my theFileManager's..." because errors will happen
          set theFileHandle to my NSFileHandle's fileHandleForWritingToURL_error_(theDataFileURL, missing value)
          --use this to write to the file URL
          --you could probably also use fileHandleForWritingToPath here,
          --but since URLs are the way of the future
          --we should use them where we can
          log theFileHandle
          theFileHandle's closeFile() --we'll use this later
     end if
end createNewFile_

I left the comments in, as they do a decent job of explaining each statement. One thing I learned is that Cocoa is big on "create the object, THEN do stuff to it" whereas traditional AppleScript lets you avoid the create the object. For example, you don't have to create a file manager object, so that you can use that to create a new blank file, which then lets you get the file handle to it so that you can write data to it. With AppleScript, you just get the file path for the new file, get the handle, write, and go.

This also follows with the save panel. You create the save panel, configure the save panel, then actually run the save panel so it displays. (Also, if anyone has some sample ASOC code for beginSheetModalForWindow:completionHandler: or
beginWithCompletionHandler: and wanted to post it in the comments or link to where it is, I'd not cry.)


Next in line...actually writing data, and then appending data to the end of an existing file. Woohoo!

Categories:     AppleScriptObjC, Applescript
Posted by John C. Welch at 16:03


Comments ()

January 17, 2010

AppleScriptObjC odds and ends

Okay, so i've been adventuring with AppleScriptObjC, because a) FaceSpan 5 is in limbo, and will be for some time, (insert wailing noises here) and b) AppleScriptObjC is what i've been raging at Apple to give me for some time now, and it's not 100%, but it's pretty close. Any posts i put up for AppleScriptObjC will have "AppleScriptObjC" as the category, so you can find them easier.

My first application is really a port of a FS 5 application I wrote that's a WiFi signal analyzer. eventually, it will show you stats, let you automatically track those stats over time, refreshing once per second, save that data to a new file, or append to an existing file, and show you a live graph of signal vs. noise in the app window. I had all this working in FS 5, so i aim to get it all working in ASAppleScriptObjC.

First, a huge, huge, huge thank you to both Shane Stanley and Craig Williams. The both of them have been a huge help to me in this, and the community is far better for having them in it.

Second, if you haven't yet gone, run to MacScripter's AppleScriptObjC forums, it's a hell of a resource, and Craig has a great set of tutorials that were, and are a monstrous help to me.

In one sense, my refusal to deal with the unending limitations of ASS have been a help, as I don't have any bad habits to break from that direction. Since I don't know Objective C, I don't have to deal with those differences either. However, the lack of ObjC knowledge is a bit of a pain in the keister when reading Apple developer docs, although not as much as I thought it might be, thanks to Craig's tutorials.

So, some quick shots that I learned:

That's enough for now, I'll start going through some of the actual code stuff I'm working on later.

Categories:     AppleScriptObjC
Posted by John C. Welch at 23:27


Comments ()

January 10, 2010

Can we please put Bruce Schneier in charge of the TSA?

He may be the only person with a fucking clue.

Categories:     Other, Politics
Posted by John C. Welch at 10:29


Comments ()

January 7, 2010

Found the prat

Remember how, in the last post, I had talked about some prat who said that you could get the same kind of in-person networking from MUGs that you can at Macworld Conference & Expo?

Found him. No surprise, it's from fucking TUAW and the specific prat is Steven Sande:

There are still a number of very active Apple-related user groups throughout the country, so you don't need to travel to San Francisco once a year to get the Apple love. Take a look at Chuck Joiner's MUGCenter.com website, and you'll see that there is constant activity all over the country. Even more than the physical meetings, there's now a huge electronic community of Apple fandom. Want to get the latest scoop on all of the new products? Check out blogs like TUAW, follow 'em on Twitter and Facebook, or even join the active chat forums on a number of sites. All of these electronic venues are much more up-to-date than an annual conference and expo. For that matter, even a weekly trip to a local Apple Store will give you tremendous insight into the new products, trends, and rumors that surround the world of Apple.

Of course, in Sandeville, you don't even need the conferences:

Some people go to Macworld Expo for the conference tracks, and in many cases those tracks are informative and taught by knowledgeable instructors. But there are other, less expensive training options available. There are local training venues in most major cities, and for those in the hinterlands, online training companies such as Lynda.com can provide the gentle hand of a tutor in a much more comfortable venue.

The fact that Sande even typed this fucking drivel, much less believes it tells me he's a complete fucking idiot, and is one of those dipshits who has either never actually spent quality time in the conference tracks, or never thought to look around and realize that the conferences were always about more than just the subject at hand. But sure. If he thinks that he's going to replicate the variety of audience and instructor content from Lynda.com, or in J. Random Training Center, and do it cheaper on a per-class basis, well, all I can say is stop doing drugs. Or do better ones.

So yeah. Steven Sande, idiot.

Categories:     Macworld Expo
Posted by John C. Welch at 10:49


Comments ()

digital.forest Where Internet solutions grow

There, a PayPal Button.

 
About the Author
Der AMB Podcast!
Use this code for your Macworld tickets!
Family
The Artwork of Melissa Findley
Diane Francis @ the National Post Eric Francis @ the Calgary Sun

Apple Amazon Links
Apple Mac OS X Server 10.5 [Unlimited]

Apple Mac OS X Server 10.5 [10-Client]

Apple Mac OS X 10.5 Leopard

Apple Mac OS X 10.5 Leopard [5-User Family Pack]

Amazon Book Links
Legacy of Ashes: The History of the CIA

The Donnas: Bitchin'

Wizards at War (The Young Wizards, Book 8)

The Demon's Sermon on the Martial Arts

The Collected Stories of Arthur C. Clarke

JavaScript and Ajax for the Web, Sixth Edition

Awakening Warrior: Revolution in the Ethics of Warfare

John Tweets Too Damned Much
FOB Links

Mac Web Writers

Techie Links

Review Victims

Individual Archives

Category Archives

Monthly Archives

Get Moveable Type!
MarsEdit: Powerful Blog Authoring Made Simple.