Thursday, December 14, 2017

View App Store reviews on Slack


AppStoreReviews is a command line tool which can send latest reviews for an app to Slack using Incoming Webhooks.

The tool can be called with following arguments:
-a AppId -c CountryCode -s WebHookId
For example: AppStoreReviews -a 584557117 -c us -s 234234/sdfsf/234324

Only AppId is mandatory. If WebHookId is omitted, reviews will be printed to standard output.

To obtain Slack Webhook URL, Slack app needs to be created with Incoming Webhooks feature.  Last 3 parts of URL are used as WebHookId.

To automate messaging process, we can tell OS to call the tool at designated times. To do that create a file containing job description and place it in ~/Library/LaunchAgents.

For example, this is job description that calls tool every day at 8:45:

 <?xml version="1.0" encoding="UTF-8"?>  
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">  
 <plist version="1.0">  
 <dict>  
  <key>KeepAlive</key>  
  <false/>  
  <key>Label</key>  
  <string>com.mijocoder.AppStoreReviews</string>  
  <key>ProgramArguments</key>  
      <array>  
         <string>/Users/mijo/Dev/appstore-cli/AppStoreReviews</string>  
         <string>-a</string>
         <string>584557117</string>   
         <string>-s</string>
         <string>234234/sdfsf/234324</string>  
      </array>  
  <key>StandardOutPath</key>  
  <string>/Users/mijo/Dev/appstore-cli/log.stdout</string>  
  <key>StartCalendarInterval</key>  
   <array>  
    <dict>  
     <key>Hour</key>  
     <integer>8</integer>  
     <key>Minute</key>  
     <integer>45</integer>  
    </dict>  
   </array>  
 </dict>  
 </plist>  

To find out more about Launch Agents check launchd.info.

Monday, October 23, 2017

Git broken on MacOS High Sierra

Today I updated to MacOS High Sierra and everything was fine until I tried to execute:
git push origin my_branch
I was greeted with this output:

failed MSpanList_Insert 0x96b000 0x419b0d8d161 0x0 0x0
fatal error: MSpanList_Insert
runtime stack:
runtime.throw(0x5f1530, 0x10)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/panic.go:530 +0x90 fp=0x7ffeefbff490 sp=0x7ffeefbff478
runtime.(*mSpanList).insert(0x8503a8, 0x96b000)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/mheap.go:933 +0x293 fp=0x7ffeefbff4c0 sp=0x7ffeefbff490
runtime.(*mheap).freeSpanLocked(0x84fba0, 0x96b000, 0x100, 0x0)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/mheap.go:809 +0x4be fp=0x7ffeefbff528 sp=0x7ffeefbff4c0
runtime.(*mheap).grow(0x84fba0, 0x8, 0x0)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/mheap.go:675 +0x2a0 fp=0x7ffeefbff580 sp=0x7ffeefbff528
runtime.(*mheap).allocSpanLocked(0x84fba0, 0x1, 0x0)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/mheap.go:553 +0x4e3 fp=0x7ffeefbff5d8 sp=0x7ffeefbff580
runtime.(*mheap).alloc_m(0x84fba0, 0x1, 0x15, 0x0)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/mheap.go:437 +0x119 fp=0x7ffeefbff608 sp=0x7ffeefbff5d8
runtime.(*mheap).alloc.func1()
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/mheap.go:502 +0x41 fp=0x7ffeefbff638 sp=0x7ffeefbff608
runtime.systemstack(0x7ffeefbff658)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/asm_amd64.s:307 +0xab fp=0x7ffeefbff640 sp=0x7ffeefbff638
runtime.(*mheap).alloc(0x84fba0, 0x1, 0x10000000015, 0x103cf)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/mheap.go:503 +0x63 fp=0x7ffeefbff688 sp=0x7ffeefbff640
runtime.(*mcentral).grow(0x8517a0, 0x0)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/mcentral.go:209 +0x93 fp=0x7ffeefbff6f0 sp=0x7ffeefbff688
runtime.(*mcentral).cacheSpan(0x8517a0, 0x84a2f8)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/mcentral.go:89 +0x47d fp=0x7ffeefbff730 sp=0x7ffeefbff6f0
runtime.(*mcache).refill(0x967000, 0x15, 0x7ffeefbff798)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/mcache.go:119 +0xcc fp=0x7ffeefbff768 sp=0x7ffeefbff730
runtime.mallocgc.func2()
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/malloc.go:642 +0x2b fp=0x7ffeefbff788 sp=0x7ffeefbff768
runtime.systemstack(0x7ffeefbff828)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/asm_amd64.s:307 +0xab fp=0x7ffeefbff790 sp=0x7ffeefbff788
runtime.mallocgc(0x180, 0x58ecc0, 0x0, 0x800000000)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/malloc.go:643 +0x869 fp=0x7ffeefbff868 sp=0x7ffeefbff790
runtime.newobject(0x58ecc0, 0x84a8b0)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/malloc.go:781 +0x42 fp=0x7ffeefbff890 sp=0x7ffeefbff868
runtime.malg(0x8000, 0x84ac60)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/proc.go:2634 +0x27 fp=0x7ffeefbff8c8 sp=0x7ffeefbff890
runtime.mpreinit(0x84b420)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/os1_darwin.go:140 +0x1f fp=0x7ffeefbff8e0 sp=0x7ffeefbff8c8
runtime.mcommoninit(0x84b420)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/proc.go:494 +0x105 fp=0x7ffeefbff928 sp=0x7ffeefbff8e0
runtime.schedinit()
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/proc.go:434 +0x79 fp=0x7ffeefbff970 sp=0x7ffeefbff928
runtime.rt0_go(0x7ffeefbff9a0, 0x4, 0x7ffeefbff9a0, 0x0, 0x4, 0x7ffeefbffad0, 0x7ffeefbffae7, 0x7ffeefbffaf0, 0x7ffeefbffaf7, 0x0, ...)
/usr/local/Cellar/go/1.6.1/libexec/src/runtime/asm_amd64.s:138 +0x132 fp=0x7ffeefbff978 sp=0x7ffeefbff970
error: failed to push some refs to 'https://github.com/mijo-gracanin/repo.git'


Googling gave me some clues and grep -irl "/usr/local/Cellar/go/1.6.1" revealed that mentioned path is located in /usr/local/bin/git-lfs.

Updating brew and relinking git-lfs solved the issue.

Thursday, March 30, 2017

PomodoroTimer

Recently I heard about Pomodoro technique and decided to try it out. Basically it's a method for increasing productivity by focusing on work for 25 minutes, having a break, and then repeating.

There is a ton of different ways to measure those 25 mins and breaks. If you type "25 min timer" in Google, you will see countdown above search results. So thoughtful.

I wanted a light and discrete timer but noticeable when it dings. Something like Thyme + ding. I'm sure that there are plenty of nice timers on Internet but till I find the right one, I can make my own and learn something in the process.

Few tomatoes later, and there it is, ticking in navigation bar: PomodoroTimer


When time reaches zero, a short sound plays and notification pops up:


The timer starts when the app is open (cmd + space > PomodoroTimer). And it's killed when tapped on it.

In first tomato I created a new Xcode project from macOS > Cocoa Application template. By default, template app shows blank app window. One way of hiding it is to just delete it from MainMenu.xib. Standard app menu is also unwanted. To disable app menu, new entry Application is agent (UIElement) = YES must be added to Info.plist.

"Blow" is one of sounds located in /System/Library/Sounds.The rest of the code is pretty self explanatory:
import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    
    private var timer: Timer?
    private let statusItem = NSStatusBar.system().statusItem(withLength: 42)

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        statusItem.action = #selector(terminate)
        
        let startDate = Date()
        let pomodoroInterval: Double = 25 * 60
        
        self.statusItem.title = formatTime(seconds: pomodoroInterval)
        
        timer = Timer.scheduledTimer(withTimeInterval: 0.5,
                                     repeats: true,
                                     block: { [weak self] timer in
            guard let `self` = self else { return }
                                        
            let elapsedTime = Date().timeIntervalSince(startDate)
            let remainingTime = pomodoroInterval - elapsedTime
            
            if remainingTime < 0 {
                timer.invalidate()
                let notification = NSUserNotification()
                notification.title = "Pomodoro!"
                notification.soundName = "Blow"
                NSUserNotificationCenter.default.deliver(notification)
            }
            else {
                self.statusItem.title = self.formatTime(seconds: remainingTime)
            }
        })
    }
    
    func formatTime(seconds: TimeInterval) -> String {
        let minutes = Int(seconds / 60)
        let remainingSeconds = Int(seconds) % 60
        return String(format:"%02d:%02d", minutes, remainingSeconds)
    }

    func terminate() {
        timer?.invalidate()
        NSApp.terminate(self)
    }
}
github   -   binary