Mac OS X Continuous Delivery (Sparkler, The missing build server for Sparkle)

UPDATE:
Sparkler is safe, it isn’t affected by the Sparkle RCE (Remote Code Execution)  vulnerability

http://appleinsider.com/articles/16/02/09/sparkle-software-updater-opens-mac-to-attack

So, You just finished the new features and bug fixes and ready to release your next version of your awesome OS X app which you choose to deliver and distribute outside the Apple’s App Store. Most probably you are using Sparkle, an amazing -open source- update framework for Mac applications, this is great, but what do you do to get into the point where users check for updates and find the new version!? and how long does it take?

The missing Sparkle component is now available (Sparkler)

Sparkler is an open source NodeJS build server for XCode’s Swift or Objective-C Mac OS Projects which aims to make the process as easy as a GET request!
Sparkler Github project

First: you need to setup the client side

Second: prepare your XCode project to be built via Sparkler

  • Add build_config.js file to your project root directory
    exports.config =
    {
     plistFilePathRelative: 'CocoaBindingReusableViews/CocoaBindingReusableViews/Info.plist'
    }
    
  • Add the private key dsa.pem file to your project root directory
    • Make sure it’s not referenced in the XCode project, this file must be kept safe

Third: setup Sparkler server:

  • Clone or download the latest release of Sparkler to your Mac server machine
  • Run NPM install
$ npm install
  • Add your project git repo to the config.js file
    {
    ..
     Sample: {
     gitRepoURL: 'git@github.com:AMTourky/CocoaBindingReusableViews.git'
     }
    ..
    }
    
  • Start Sparkler server (you can use PM2 for a better production process manager )
$ node index.js

Now, you have multiple distribution lines to deliver your application versions, there is a versions feed url for every branch of your project. For project ‘Sample’ on branch ‘dev’

sparkler_address/feed?projectName=Sample&branchName=dev

the build URL for ‘dev’ branch is:

sparkler_address/build?projectName=Sample&branchName=dev

there are optional versioning parameters for build action (major=true, minor=true or batch=true) that will affect the released version

The output build will automatically get the new versions built from the same projectName and branchName as long as the Sparkler server is up and running and reachable

Sparkler still in it’s early stages, check every now and then for more features

Features to expect soon:

  • Support xcode project test cases so it can really be called a ‘Continuous Delivery’
  • Support private key to be outside the project git repo
  • Insights analytics

 

Advertisements

Reusable XIBs in cocoa binding (DRY Views) NSView, Part 2

DryViews In Part1 wasn’t flexible enough to be used widely in a big macOS application I’m working on, so the need for a more flexible and dynamic DryView was pushing.

Introducing, DryView the NSView

A NSView subclass that can be dynamically loading a nib file, set object controller to do whatever binding you need nesting whatever the number of DryViews without a single line of code!

screen-shot-2016-09-26-at-11-02-22-pm

Let’s say you have a core data model object -you can use any Objective-C or Swift NSObject subclasses not just NSManagedObject- named Item and SubItem children of BaseItem super class. To View Item os SubItem you need to show the shared properties intProperty and stringProperty. Using DryView you will no longer need to duplicate views to view each model’s property because you can reuse the BaseItem’s inspector view:

screen-shot-2016-09-26-at-10-58-51-pm

screen-shot-2016-09-26-at-10-58-41-pm

And create a new inspector view for the Item by adding a date field and subItem table only and reuse the BaseItem view for the rest of properties, also the SubItem can be done the same way.

Now combine a view that shows an Item with it’s children inspecting the selected child in the right side of the view to get this:

screen-shot-2016-09-26-at-10-59-00-pm

All what you need to do is to add a custom view, change it’s type to DryView, set the nibName you want to load and reference The NSObjectController to be used in binding if any, and you are ready to GO!!

This slideshow requires JavaScript.

 

Without duplicating a single view, compose your own symphony of views and go Dry all the way even in views:

animated.gif

You can find DryView class here and the sample app I made for this post is here

Need an Objective-C or Swift 3 version, stay tuned!

A Script a day, keeps boring away!

Being a software engineer makes life easier, it can save you time, money and hassle. It makes things just easier, cooler without sacrifices!

My Favorite radio show shifted their air time 2 hours!! No Problem

In my way to work, I used to listen to a morning radio show, but one day, they changed it’s air time! one option is to go work late OR!! Create a cron job that runs every day at the show time and download from the radio live streaming url using wget for an hour and save the file to my dropbox folder, so I can play it from my mobile at the end of the day back to home!


0 2 * * * /usr/local/bin/wget -O /Users/AMTourky/Dropbox/Mega_FM/megaFM_3eliesh_$(date '+%m-%d__%H-%M').mp3 http://mgstrm9.twesto.com:7281/stream1.nsv ; sleep 3600; kill $! ; /dev/null || : ;

A 95% discount snap offer on an item, Great BUT!!

Untitled 2

 

The largest e-commerce site in the Arab world made a snap offer on an item, a 95% off the price, from 400 EGP to 20 EGP BUT it’s a little bit tricky! the item will be available in random times for 3 days, so the probability of getting one is too low!

This time I needed something that can do more than just hitting a URL! a nodeJS script that checks the site’s homepage every minute to see if the item is available, if yes, send me a notification on my mobile (I used a dead simple push notification service PushBots).

var checkItem = function()
{
	async.waterfall(
	[
            // used wget instead of the 'http' node package because they didn't return the page normally!
		function(callback) { exec('wget  https://www.home/page/url\ -O page.html', callback); },
		function(stdout, stderr, callback) { fs.readFile('page.html', 'utf8', callback); },
		function(pageContent, callback) { isItemAvailable(pageContent, callback); }
	],
	function(error, isAvailable)
	{
		if ( isAvailable ) { notifyMe(); }
	}
	);
};
var isItemAvailable = function(content, callback)
{
	var lines = content.split('\n');
  	for(var i = 0 ; i < lines.length ; i++)   	{   		var line = lines[i];   		if(line.indexOf('R207') >= 0)
  		{
  			var availabilityLine = lines[i-17];
  			var priceLine = lines[i+9];
  			if(availabilityLine.indexOf('sold_out') < 0 && priceLine.indexOf('20') >= 0)
  			{
  				callback(null, true);
  			}
  			break;
  		}
  	}
	callback(null, false);
};
var notifyMe = function()
{
	instapush.notify(...);
};
setInterval(checkItem, 60*1000);

 

2016-01-23 23.35.14
It arrived the second day 😀

Waiting on online customer service queue for long time!! No Problem

On one of the worst online customer service I’ve ever dealt with they had a very long queue, they start responding after about 30 minutes, and if I didn’t respond on a minute or two, they skip me and pick the next customer. Waiting up to 30 minutes in one page or checking it every minute is unacceptable, but for a software engineer, this can’t be the case! a simple event listener on the chat dom element to be triggered when the agent responds and an Alert will do the trick, I pasted the code below into the developer console and went doing my work!

var isNotified = false;
document.addEventListener('DOMNodeInserted',
	function(evt)
	{
		// ignore the time counter since I joined the queue
		if (!isNotified &amp;&amp; evt.srcElement.className != 'digit')
		{
			alert('Here we go!!');
			isNotified = true;
		}
	}, false);

A lot of times I’m interested in some data from a specific web page, or want to know how frequent it change, or when it change, being a software engineer make all of this an easy task, an automated task, I can create a robot to do it for me. Some times it just require a technology geek to accomplish a cool task; adding a large HDD to home network and give the family an extra storage to their phones/tablets or using an old phone as a surveillance camera while traveling and setup a DDNS service to check it remotely.

Being a software engineer opens a new dimensions in one’s life, changes perspectives and gives a new flavor to almost every thing!

Reusable XIBs in cocoa binding (DRY Views) The Tabs, Part 1

DRY Views

Classes should be reusable, functions should be reusable and any code we write should be highly reusable, a DRY code is a target yes, but what about a DRY views!?

Views also should be reusable, and when it comes to XIBs in MAC OS X app that uses cocoa binding it becomes a little bit tricky.

Our sample app is a master details app which has an inspector window which inspects the selected item whether it’s a master item or detailed item. you can find the source code here.

The master view contains a list of Managers, and yes as you guessed, the detailed view is a list of Employees managed by that Manager.

Untitled 5

Both ‘Manager’ and ‘Employee’ have common properties, and yes again you are right, we will create a super class ‘Person’ to keep the common properties like first name, last name and date of birth. This is good -as far as the model classes are concerned- but what about the inspectors! we then must have two separate views to inspect a Manager and an Employee! but both have common properties, why we should add first name, last name and date of birth fields twice in every inspector view?!!!

Duplicate controls to inspect common properties

 

let’s create a common properties inspector view first which can inspect Person’s properties, another inspector which can inspect Manager’s and one more for Employee’s properties.

One of the advantages of cocoa binding is that we don’t need -almost- any code to for simple CRUD operations on Model classes(using NSController‘s subclasses) nor to glue the UI controls with the model’s properties, so we don’t need Inspector view classes! except one to load the right inspector XIBs into a general proposes NSTabView (for simplicity) and that’s it!

InspectorWindowController.swift

class InspectorWindowController: NSWindowController
{
  var inspectedPerson: Person?
  @IBOutlet var tabView: NSTabView?

  func inspect(person: Person)
  {
  }
}

Inspector xib should be as simple as a tab view item referenced in the InspectorWindowController, it’s the container of the real inspectors that will be loaded later.

inspector
https://amtourky.files.wordpress.com/2016/01/inspector.png

Setup Inspecting flow

We are interested in the selected Person, so an observers on the selection property of the managers and employees will do the job, so on selection change, we will call inspect function of the inspector view controller passing the selected manager/employee.

so inspect function will be something like this, we will get the inspected person, remove any tab items if any to clean the views of old person, then load the new tabs required by the new selected person.

  func inspect(person: Person)
  {
      self.inspectedPerson = person
      self.removeAllTabItems()
      self.loadTabViewItemsForInspectedPerson()
  }

And as elegant the views are, the code behind the scene must be as well, so we are splitting the inspection into three main functions, remove tabs, load the new tab items and then populate the main tab view with the loaded tab items.

  func removeAllTabItems()
  {
      if self.tabView?.tabViewItems.count != 0
      {
          for tabViewItem in self.tabView!.tabViewItems
          {
              self.tabView?.removeTabViewItem(tabViewItem)
          }
      }
  }

 

  func loadTabViewItemsForInspectedPerson()
  {
      guard let theInspectedPerson = self.inspectedPerson
      else { return }

      for tabInfo in theInspectedPerson.inspectorTabsInfo
      {
          if let theNibName = tabInfo, theTabTitle = tabInfo[&amp;amp;quot;tabTitle&amp;amp;quot;]
          {
              let newTabViewItem = NSTabViewItem(identifier: &amp;amp;quot;&amp;amp;quot;)
              newTabViewItem.label = theTabTitle

              self.populateTabViewItem(newTabViewItem, fromNibName: theNibName)

              self.tabView?.addTabViewItem(newTabViewItem)
          }
      }
  }

 

  func populateTabViewItem(tabViewItem: NSTabViewItem, fromNibName nibName: String)
  {
      var topLevelObjects: NSArray?
      NSBundle.mainBundle().loadNibNamed(nibName, owner: self, topLevelObjects: &amp;amp;amp;topLevelObjects)

      guard let theTopLevelObjects = topLevelObjects
      else { return }

      for object in theTopLevelObjects
      {
          if let theTabView = object as? NSView
          {
              tabViewItem.view = theTabView
          }
          else if let theTabObjectController = object as? NSObjectController
          {
              theTabObjectController.content = self.inspectedPerson
          }
      }
  }

NSBundle.mainBundle().loadNibNamed ??

So what we are loading exactly with the class function loadNibNamed? we are interested in two items:

NSView

The view itself, the NSView instance the xib has to set it as the view of the new tab we just created.

NSObjectController, where views get DRY!!!

The NSObjectController which the views are bound to, remember, those xibs have no owner at all, and the object controllers’s content isn’t bound to anything till now!

Untitled 7 not content object
https://amtourky.files.wordpress.com/2016/01/untitled-7-not-content-object.png

Till the the inspector know what person we are inspecting, then we are feeding the object controller with that person as the content object.

else if let theTabObjectController = object as? NSObjectController
{
  theTabObjectController.content = self.inspectedPerson
}

The top level objects loaded from xib will contain the NSControllers subclasses, pick the one/multiple that needs content object/set/array.. and start feeding them with whatever the normal view owner would do!

But what is person.inspectorTabsInfo!

Every model class will declare the xib files which can inspect it’s properties, a computed property that returns an array of objects describing a tab, contain a xib file name and title of the tab view item, Person declares PersonInspector xib, Employee will return the super inspectors (which is PersonInspector) and add to it EmployeeInspector and Manager also will return PersonInspector and ManagerInspector.

class Person: NSManagedObject
{
    var inspectorTabsInfo: [[String: String]]
    {
        return [[&amp;amp;quot;tabTitle&amp;amp;quot;: &amp;amp;quot;Basic Info&amp;amp;quot;, &amp;amp;quot;tabNibName&amp;amp;quot;: &amp;amp;quot;PersonInspector&amp;amp;quot;]]
    }
}

class Employee: Person
{
    override var inspectorTabsInfo: [[String: String]]
    {
        var parentTabsInfo = super.inspectorTabsInfo
        parentTabsInfo.append([&amp;amp;quot;tabTitle&amp;amp;quot;: &amp;amp;quot;Employee Info&amp;amp;quot;, &amp;amp;quot;tabNibName&amp;amp;quot;: &amp;amp;quot;EmployeeInspector&amp;amp;quot;])
        return parentTabsInfo
    }
}

class Manager: Person
{
    override var inspectorTabsInfo: [[String: String]]
    {
        var parentTabsInfo = super.inspectorTabsInfo
        parentTabsInfo.append([&amp;amp;quot;tabTitle&amp;amp;quot;: &amp;amp;quot;Manger Info&amp;amp;quot;, &amp;amp;quot;tabNibName&amp;amp;quot;: &amp;amp;quot;ManagerInspector&amp;amp;quot;])
        return parentTabsInfo
    }
}

And now we have a DRY cocoa binding views that work magically anywhere by just feeding it with the content it’s designed for!
You can also drive from the ObjectController another array controllers for more complex scenarios, like if you want to show the manager’s employees in the ManagerInspector, you would then create a new NSArrayController which content set is bound to the ObjectController.employees
Or even a complete different models by feeding multiple NSControllers at populateTabViewItem function.

 

sample
https://amtourky.files.wordpress.com/2016/01/sample.gif

Here is the source code of the sample app.