La Vita è Bella

Sunday, March 19, 2017

SmartThings, MyQ and Scala

SmartThings

Ever since I became a homeowner last year, I started to explore the IoT/home automation world.

The first IoT product I decided to purchase was Schlage Connect deadbolt. The only problem with it is that to use something more than passcode, I need to pair it with some home third-party automation hub.

After some research, I chose SmartThings in the end. It worked great with the Schlage deadbolt.

After the lock and the hub, the next step was garage door. The only garage door controller SmartThings "supports" out of the box is GD00Z-4, so I just bought that.

Besides basic functions (e.g. check status and open/close from your phone), SmartThings also provided routines and "SmartApps". SmartApps are the real gem here: besides the ones published on SmartThings' "Marketplace", you can also just use the developer site to write your own SmartApps (the developer platform is based on Groovy) and publish to yourself. Since it's just so easy to get code running, a lot of developers just don't bother to get their apps reviewed and published in the "Marketplace". Instead, they just share the source code, and anyone can just copy the code to their own developer console.

At first I found an app that can close the garage door after N minutes, and did some modifications so that it can send me a notification at 3 minutes, close the garage door at 5 minutes, and did a check again at 6 minutes to see if it's still open (because regulations, the remote closing of garage door could fail because of obstacles and things).

That's useful, but not really "smart". What I want is auto open the garage door when I'm driving home, and auto close it when I'm leaving.

So I purchased an Arrival Sensor to put in my car, and starting to write my own SmartApp.

This should quite simple, right? Just open the garage door when the arrival sensor's status changed from leave to present, and close the garage door when the arrival sensor's status changed from present to leave. That's what I did initially, and that worked great. At least for a while.

Until one day, the arrival sensor just went haywire, starting to flipping status randomly, while it's physically in my garage, unmoved. That caused my garage door to open unexpected.

So I changed the code, added a condition that the auto open will only happen if it's more than N minutes after we last tried to auto close it. And that worked great. You know the catch, for a while, again.

One night it went haywire again, and this time it flips status on a longer interval -- longer than the 5 minutes I set on my version 2. My garage door opened several times that night.

So it's time for version 3. This time I changed the condition. Instead of only open it if it's N minutes after last close, I only auto open it after a true auto close -- defined as it really closed the garage door, or the garage door was already closed, but that's no longer than N minutes before the app tries to close it (for the case when I manually closed the garage door when leaving).

Version 3 worked great for several months now, I think this is the mature version. You can find the code on this gist.

MyQ

After I happily used my Presence and Garage Door SmartApp for a couple of months, one day the smart garage door controller just stopped working.

Looking at Amazon reviews, that seems to be a very common problem: A lot of users also had it suddenly stopped working after couple of months. The manufacturer didn't provide any warranty, instead they kicked the ball to the sellers. We contacted Amazon, and got full refund (as it's still within the first year).

Because of this is a widespread issue, I decided to buy something else. One of my friend once has his garage door motor broken, so he was forced to upgrade his garage door motor, and the new one he purchased has MyQ. He was happy with that, so I decided to buy MyQ this time (also it comes from the same manufacturer of my garage door motor, just my garage door motor doesn't have MyQ built-in).

I purchased the MyQ package, installed it, and installed their app. Wow that's really disappointing. Yes the app can check the status of the garage door and open/close it, but there's literally nothing more. They don't have any integration (they do have a Nest integration, but that means I can control Nest in MyQ app, not vice-versa. Like, what's the point of that?), and they don't even have any open API.

Digging around the internet, someone actually reverse engineered the closed API they use on their mobile apps, so they do "have" an API, just there's no guarantee when will it be broken.

At first I decided to write an Android app to do the automation. The idea was simple, I define an interface to detect that I'm on the car (it could be from bluetooth connection, or detected that Android Auto app is running, etc.), and an interface to detect my presence (it could be geofence, or from WiFi connection). Combining the two interfaces and the MyQ API, I could implement the automation I wanted.

On first step I implemented the MyQ API on Android, nothing special here. Then I implemented the bluetooth car detection.

The WiFi presence detection didn't work as I expected, and geofence will require some extra UI work, so as a temporary workaround, I implemented an Android Auto notification instead: the app send an Android Auto notification when it detected it's in the car, and when I replied the notification with keyword "open" or "close", it uses MyQ API to open or close the garage door. That way, although it's not automation I wanted, at least I can control my garage door by voice, which is supposed to be much safer than fiddling with the MyQ app.

But that didn't work as I expected either. I'm not sure if it's because of Nexus 5X's infamous low memory, but the Android Auto reply will have a very long latency before it actually works. Often I tried to reply the notification with "open" when I'm near home, and I still need to manually open my garage door when I'm actually home, and after I already parked the car and in the living room, the reply actually worked and the garage door is (finally) open. That's not useful at all.

So I gave up on the Android app, and looked back at SmartThings.

SmartApps are not the only thing you can write on SmartThings' developer platform. You can also implement custom Device Handlers, to bring other device into SmartThings platform. Someone actually tried to do the same thing a few years ago, but their device handler no longer work, probably because the MyQ API they used no longer works. As I result, I took a night to implement the new, working MyQ API in the old device handler, and it actually worked. I can finally use my garage automation SmartApp with MyQ garage controller again, at least before MyQ kills the API I use (I hope they'll provide real open APIs if they ever decided to do that).

Scala

As I'm using closed MyQ API, there's an insecure fear that my SmartThings device handler could break any day. The SmartApp will actually send a notification when it auto open or close the garage door, but as I'm using Android Auto now, Android Auto will only show its compatible notifications, and hide everything else (which makes sense, because you don't want to distract drivers). And SmartThings notifications belong to "everything else".

With my experience with that failed Android app attempt, I thought maybe I can write another Android app, this time just translate normal notifications into Android Auto compatible notifications, so I can know that it worked.

There's a catch, I don't want to use Java this time. Having a day job writing Go, writing Java code at night is quite a contextual switch makes my head aches. So I decided to use this project as an opportunity to learn Kotlin or Scala.

There's another catch, I don't want to use Gradle or Android Studio. I wrote my codes in vim, and I want to use some better building tools, preferably Bazel.

They both have Bazel support (Scala through Bazel offically and Kotlin through third-party Skylark rules), but neither supports Android.

There are some comparisons on the internet prefer Kotlin over Scala, also Scala has some bad reputation about its learning curve, so I slightly leaned towards Kotlin.

But Kotlin has no documentation about how to use their kotlinc compiler CLI to build Android apps. I have absolutely no clue how to make it work outside of Gradle. Scala, on the other hand, has sbt. It's not Bazel, but at least I like it much more than Gradle.

So I took one week to wrote the app in Scala. The result is on GitHub. This is quite a simple app so there's nothing fancy there. I didn't have a chance to use a lot of advanced Scala features. But I do like the features I used, like foreach over an Option to do null handling.

When I tried to publish it on Google Play last night, I got a problem: Android Auto only allow one type of notifications, and that's peer-to-peer messages. Because my app send notifications that's not peer-to-peer messages, it's rejected. I guess that's why SmartThings didn't make their notifications compatible with Android Auto in the first place.

If you find it useful and don't mind sideloading apps on your Android phone, you can find the apk on the GitHub release page. But you probably mind it, as do I (I don't sideload any app on my phone). So going forward I need to find another way, at least before Android Auto loosen their notification restrictions.

One idea is to make it a chat-bot with an chat app with existing Android Auto support, and the chat app I'm thinking about is Telegram. The reason is that it has both quite good Android Auto support, and bot API support.

So that is probably what I will do in the next few weeks. Please stay tuned. Thinking about it, maybe I'll actually make a poor man's PushBullet accidentally :)



tags: , , , ,

18:52:00 by fishy - dev - Permanent Link

no comments yet - no trackbacks yet - karma: 4 [+/-]

Tuesday, February 28, 2017

Comparing []byte's in Go

As I'm new to Go, I think it's quite understandable that I'm not familiar with the standard library yet, and reinvented the wheel sometimes.

One day I need to compare two []byte's. Since I'm not aware of any implementation in standard library, and this is simple enough, I wrote my own comparing function. It's something like:

func equal(b1, b2 []bytebool {
  if len(b1) != len(b2) {
    return false
  }
  for i, b := range b1 {
    if b != b2[i] {
      return false
    }
  }
  return true
}

A few days later, I saw my colleague used reflect.DeepEqual in our codebase. I thought "Oh that's cool! No need to reinvent the wheel!" So I removed my own implementation and used reflect.DeepEqual instead.

A few more days later, I accidentally discovered the existence of bytes.Equal. This is obviously faster than reflect.DeepEqual (because its feature is much simpler), so I'm going to replace them. But before that, I decided to do a simple benchmark test to see how much faster.

(In case you are not familiar with Go or Go's testing package, Go provided a very easy to use benchmark framework in it. Some might think the lack of features like Assume makes the testing code mouthful. I kind of agree with that, but I'm also mostly OK with that.)

The full benchmark test code can be found in this public gist. I'll just post the result here

BenchmarkEqual/reflect.DeepEqual-1024-4         	   20000	     91193 ns/op
BenchmarkEqual/equal-1024-4                     	 3000000	       544 ns/op
BenchmarkEqual/bytes.Equal-1024-4               	50000000	        22.6 ns/op
BenchmarkEqual/reflect.DeepEqual-1048576-4      	      20	  89556304 ns/op
BenchmarkEqual/equal-1048576-4                  	    3000	    536891 ns/op
BenchmarkEqual/bytes.Equal-1048576-4            	   30000	     44613 ns/op
BenchmarkEqual/reflect.DeepEqual-67108864-4     	       1	5801186044 ns/op
BenchmarkEqual/equal-67108864-4                 	      30	  37011544 ns/op
BenchmarkEqual/bytes.Equal-67108864-4           	     200	   8574768 ns/op
BenchmarkNonEqual/reflect.DeepEqual-1024-4      	 5000000	       280 ns/op
BenchmarkNonEqual/equal-1024-4                  	500000000	         3.46 ns/op
BenchmarkNonEqual/bytes.Equal-1024-4            	300000000	         4.56 ns/op
BenchmarkNonEqual/reflect.DeepEqual-1048576-4   	 5000000	       272 ns/op
BenchmarkNonEqual/equal-1048576-4               	500000000	         3.44 ns/op
BenchmarkNonEqual/bytes.Equal-1048576-4         	300000000	         4.52 ns/op
BenchmarkNonEqual/reflect.DeepEqual-67108864-4  	 5000000	       269 ns/op
BenchmarkNonEqual/equal-67108864-4              	500000000	         3.42 ns/op
BenchmarkNonEqual/bytes.Equal-67108864-4        	300000000	         4.50 ns/op

In short, bytes.Equal is usually 3 orders of magnitude faster than reflect.DeepEqual when the result is true, and 2 orders of magnitude faster when the result is false. (Also, my own implementation is only 1 order of magnitude slower than bytes.Equal when the result is true, and slightly faster when the result is false :)

And of course I replaced reflect.DeepEqual with bytes.Equal in our code base as appropriate.



tags: , , , ,

23:33:17 by fishy - dev - Permanent Link

no comments yet - no trackbacks yet - karma: -1 [+/-]

Saturday, February 11, 2017

Some Go memory notes

I recently did some work on a backend server written in Go, and one of the work is to reduce the memory usage, as the old code will use ~110GB of memory steadily (this is not memory leak), mainly because of we used to strive for the speed of coding, not efficiency. With the help of Go's standard pprof tool (which is wonderful), we managed to lowered memory usage down to less than 15GB steadily. Here are some notes I learnt from the process:

You don't always want to read io.Reader into []byte

When you are passing some data around, they usually come in one of the two forms in Go: either an io.Reader (or sometimes io.ReadCloser, which is kind of compatible with io.Reader), or []byte.

[]byte is something more permanent: It just sits there, you can do whatever you want with it. io.Reader, on the other hand, is more fragile: You can only read it once, and it's gone after that (unless you saved what you just read).

Because of that, sometimes it's very tempting to read that io.Reader you just got from some function into []byte (using ioutil.ReadAll) to save it permanently.

But that comes with a price: you need memory to save that []byte you just read. Do you really need that?

Most of the functions you need to pass the data on will gladly accept io.Reader instead of []byte as the parameter, and there's a good reason for that: It's always trivial to create an io.Reader out of []byte (both bytes.NewReader and bytes.NewBuffer can achieve that goal, I did some benchmark test and there're no significant performance difference between them, but I still slightly prefer bytes.NewReader, because Occam's razor). If you only need to pass the data to a single function to process once, there's no need to read it into []byte and convert it back into io.Reader again.

Sometimes even if you need to process the data in two different functions, you still don't need to call ioutil.ReadAll. One common use case is that you have a content of a file in the form of io.Reader, and you want to get the content type first using http.DetectContentType before sending that file to your user. You don't need the whole content of the file for that. http.DetectContentType only needs the first 512 bytes. So you could just wrap your reader with bufio.Reader, then Peek 512 bytes for http.DetectContentType, after that you can just send your bufio.Reader to the function to your user.

Beware of []byte in JSON

In Go's JSON package, []byte is "encoded as a base64-encoded string". This is a very reasonable design choice, but if you are using JSON to pass large chunk of []byte between machines, you might want to think twice.

On sender's side, you first need to save the whole chunk of []byte into memory, then when doing the JSON encoding, you will need to save both the original chunk of []byte, and the whole encoded base64 string, which is larger than your original chunk by design. Congratulations, you just more than doubled your memory consumption.

It's the same on receiver's side. At first you need to save the whole JSON string, including the very large base64 string, into memory (because Go's JSON decoding is not really a streaming friendly operation, although Go's base64 decoding is streaming friendly, that doesn't help in this case). During the decoding, it will allocate another chunk of memory to save your original []byte. So in the end you'll spend the same or more memory as the sender side.

If you really need to pass big chunk []byte between machines, JSON is not your friend. You should use some binary and streaming protocol instead. Both gRPC and thrift will do that job nicely, and they both have good Go support.

Avoid "adding" strings directly

Imagine you have a function to join strings together like this (No you don't really want to implement this. strings.Join already implemented this feature for you. This is just a simple example):

func JoinStrings(parts []stringstring {
  var s string
  for _, part := range parts {
    s += part
  }
  return s
}

Every time you do s +=, Go will actually allocate a totally new, bigger string, copy previous s over, then append new part at the end. This wastes both CPU (for copying) and memory.

Instead, you can use bytes.Buffer, which works like StringBuilder in Java:

func JoinStrings(parts []stringstring {
  var buf bytes.Buffer
  for _, part := range parts {
    buf.WriteString(part)
  }
  return buf.String()
}



tags: , , ,

02:52:29 by fishy - dev - Permanent Link

no comments yet - no trackbacks yet - karma: 5 [+/-]

Thursday, November 24, 2016

OneKey

This is an open source project I started 5 years ago, and just "finished" recently.

Back in 2011, my friend tension7 released a Chrome extension called One Password. It has a simple idea: using HMAC-MD5 to generate unique passwords for different sites.

I thought that was a good idea, and started to use it myself. (The idea is actually called "deterministic password manager/generator", and there are some discussions about it recently. I agree that it doesn't make your password stronger, but it do works better in certain cases. I use it on some sites myself, but not all of them.) I wrote a python version of it so that when I can use it easily while I'm not on Chrome.

And I also started writing an Android app for it. This is the Android app.

The sad thing is just that there're too many distractions in life, so I never found time to finish it. I made it "usable" at some point of 2013 or 2014, put it on my phone, and just put it away.

But I suddenly had some time due to various reasons recently, so I finally finished it. By "finish it" I mean I did a 1.0 release and listed it on Google Play, 5 years after I started the project.

There are still some TODOs, for example I would like to add the feature to save the master password with fingerprint, but I didn't figure out a good way to design the UI to make it work. But it's mostly feature complete, with highlights:

On the technical side, I chose Buck as the building system for this project, because it was the build system used by my day job project, and it worked great. I also tried to move it from Buck to Bazel, but Android's incompatible with Java8 is causing a lot of problems. I will probably switch to Bazel after that is sorted out.

Anyway, if you found the idea of deterministic password generator appealing to you (at least in certain cases), please give it a try. I hope you enjoy it.



tags: , , ,

21:54:18 by fishy - opensource - Permanent Link

no comments yet - no trackbacks yet - karma: 1 [+/-]

Friday, December 11, 2015

Let's Encrypt!

Thanks to Let's Encrypt, this blog is now serving via https (and https only):

Screenshot of https certificate

In the process of enabling https, I also switched my host from Dreamhost to Google Cloud, and switched to nginx as httpd. (And Dreamhost announced Let's Encrypt support after I made the switch)

The only problem with Let's Encrypt is that the certificate is only valid for 90 days (ok no support of wildcard domain might also be a problem, but I don't feel it), which means I need to renew my certificates often. Luckily that can be done via a monthly (or bi-monthly) cron job.

This is the code snippet of my nginx configuration to make both https only and Let's Encrypt ACME verification work:

server {
        listen 80;
        listen [::]:80;

        server_name    yuxuan.org www.yuxuan.org wang.yuxuan.org;

        location /.well-known/acme-challenge/ {
                alias /var/www/challenges/.well-known/acme-challenge/;
                try_files $uri =404;
        }

        location / {
                return 301 https://$host$request_uri;
        }
}

And this is the script to be put into crontab (I use the official client from Debian experimental):

/usr/bin/letsencrypt certonly --renew-by-default --webroot -w /var/www/challenges -d yuxuan.org -d www.yuxuan.org -d wang.yuxuan.org

That's it! Please consider donating to Let's Encrypt!



tags: , , , ,

23:26:36 by fishy - linux - Permanent Link

no comments yet - no trackbacks yet - karma: 14 [+/-]

Thursday, November 10, 2011

I tried to migrate from Aperture to Lightroom but rolled back

UPDATE: with Aperture 4 Beta released with geotag support, I finally migrated to Lightroom (4), and will buy it on Day 1!

I bought my first "RAW camera" at the end of 2009, and found that iPhoto is not enough for me. So I purchased Apple Aperture 3 at February 2010, almost immediately after it's released. One reason is that I was used to iPhoto and Aperture carried on most of the things, another reason is that Adobe Photoshop Lightroom 3 is on its way but still about half year away at that time.

I'm just an amateur photographer, snap some photos for my life record and show them on Flickr. I don't sell my photos for life (but will be glad if you're interested :P). So I don't need something extremely professional to process my photos. Generally I just use the "Auto Enhance" in Aperture to tune my photos, and custom white balance sometimes. I rarely use other tunes in Aperture. So generally I'm happy with Aperture.

But there're still something drive me crazy, like the slowness, and some bugs. I was upset about some of the bugs, so serious considered to migrate to Lightroom. I downloaded Lightroom trial and borrowed a USB HD to move my photos (~80GB) from Aperture library into Lightroom, and seriously used it for a couple of days.

But, there's always a "but", Lightroom is not perfect and there're still something I don't like. And I found out that the "bugs" drive me crazy is actually something wrong with my Mac, not Aperture. So I finally decided to cool down and not to invest another $150 on Lightroom, and keep on Aperture, at least for now.

Here're some of my comparisons.

What Lightroom did great?

First I'd like to talk about some of Lightroom's advantages, and of course, they are Aperture's disadvantages:

What Aperture did great?

There're still something Aperture did better than Lightroom. Most of them are just useless to professionals, but they are good for amateurs:

Between speed and geotag, I chose geotag. But I do hope Apple can match Lightroom's speed, or Adobe can add native geotag support to Lightroom. I just hate Aperture's slowness, and Lightroom's lack of geotag support. For now, I can only suffer the slow.



tags: , , ,

15:01:42 by fishy - mac - Permanent Link

no comments yet - no trackbacks yet - karma: 150 [+/-]

Saturday, January 22, 2011

Fix epubs

The wonderful epub reader for Android, Aldiko, released a major update recently. Besides some great new features/improvements, it also broke something: all Chinese characters in Pro Git Chinese version can't be displayed. But it didn't simply broke Chinese epubs at all. My other Chinese epubs are just fine.

I contacted Aldiko for help, they just replied claiming that I need to embed Chinese font into epub for them to display, and Adobe Digital Edition have the same problem. I tried my epubs with Adobe Digital Edition and it's really the same. But after I looked into my good and broken epubs (they are simply zip archives with metadata, html, css and pictures inside), I can't find embedded Chinese font (in the good ones, of course) at all.

Instead, I found something else interesting: all my good epubs have an "xml:lang" parameter set to "zh-CN" in every html tag, and the broken one didn't.

So I add that parameter to every html file in progit-zh.epub, and repack the file, now it works (in both Aldiko and Adobe Digital Edition)!

To simplify this work, I wrote a python script to do that automatically. It will unpack the epub file into a temp directory, add "xml:lang" parameter to every html file if didn't present, and then pack the epub back again. It will also output the replaced lines into stderr, like:

./progit_split_000.html:
<html xmlns="http://www.w3.org/1999/xhtml">
->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">

Get it from my script repo on GitHub, and change the LANG constant if needed.

If you have other ideas about it, please let me know.



tags: , , ,

21:28:08 by fishy - i18n.zh - Permanent Link

no comments yet - no trackbacks yet - karma: 27 [+/-]

Monday, January 10, 2011

Purge Strikes Back to Android!

A Little Bit History

Back in 2005, after graduation from college, I got my first job at TrendMicro. After I got paid for enough money, I went to the store and brought a Palm Treo 650 home. That's the dream phone of that time. It have good hardware spec and more important, PalmOS.

My Treo 650 was broken at 2007. The radio module is died. So I can't use it as a phone, but can still use it as a PDA. I've made a nature choice: bought a Treo 680 to replace it. The reason is quite simple: it have PalmOS inside.

But the bad thing is I lost my Treo 680 at the end of 2007, left it on a table of a restaurant and never got it back again. This time I didn't buy Centro (but I recommended it to my then roommate :P). I decided to try something new. So I bought a Symbian phone (Nokia E51), Symbian looks like something comes from Stone Age compare to Palm OS. Then I bought a Windows Mobile phone (Sony Ericsson Xperia X1), but realized later that I do hate Windows Mobile. I bought a Samsung Galaxy (not to be confused with Samsung Galaxy S) at 2009, as I thought Android was mature enough to use. And I do love Android! But the lack of support and upgrade from Samsung makes me unhappy.

Finally, Google released Nexus One and I ordered one at the first time. This becomes the first phone after Treo 680 that I'm still happy after using it for half year, and I'm still happy with it while it's nearly a whole year already for now.

But...

But there's still something I missed from Palm, like "Purge":

Purge

(I dug out my broken Treo 650 today, blow away the dusts, insert the battery, and it still works! so comes this photo.)

"Purge" is a simple feature to do the cleanup work: delete all old messages older than N days. Yes Android is much more powerful compare to Palm OS in the old days, and it can keep "almost everything" (actually for SMS, it will keep only the recent 200 messages per conversation and automatically delete old ones by default). But I don't like to keep too many meaningless "everything", I prefer the old Palm way.

So I took the last weekend to make Purge Strikes Back!

Purge Strikes Back!

YOU HAVE BEEN WARNED!

Something I must mention is that for SMS deletion, I used some undocumented API. What did this mean? It means that this app may or may NOT work for you. It may or may NOT work as you expected. It may DELETE ALL YOUR SMS even you didn't mean to. It works on my Nexus One with Froyo and system Message app, it may not work with other phones, Android versions or Message apps. And it may break anytime even it works fine for you now. You take it totally on your own risk.

The call log deletion is implemented with system API, which should works fine, but I took no responsibility for your unexpected data loss, either.

So you'd better BACKUP YOUR SMS BEFORE TRYING IT. Personally I'm using SMS Backup + to do this for me and it works fine (it also backups call logs, too). There may be better ones but this is a choice you can consider.

Again, YOU HAVE BEEN WARNED!

How to Get It

Download it from its GitHub repo. It's free. You can also get the source code there, licensed under GPL v3.

I'll publish it to the Android Market in the coming days.

What to Expect in the Future

Acknowledgement

Some UI elements took from another awesome open source Android app Financisto, which is licensed under GPL v2.

Link

project homepage



tags: , , ,

23:53:07 by fishy - opensource - Permanent Link

no comments yet - no trackbacks yet - karma: 39 [+/-]

Wednesday, October 13, 2010

Say "Goodbye and Thank you" to POPFile

POPFile is an awesome mail classifier works as a local POP3 proxy. It's written in Perl and use a web UI so it can run on any platform.

I started using it back from about 2004. First I use it to filter spams, later to filter work mail, personal mail and important mails apart. Time moves on, as I stop using POP3 on Gmail, the main job for POPFile became to picking not-so-important script generated reports from other work mails (combined with Thunderbird filter based on "X-Text-Classification" header).

As I also use Gmail for work mail now at Google, I have only a few POP3 accounts that rarely get some non-spam mails. So it's finally the time to say goodbye to POPFile. As a record, here's the final statistics for my POPFile history, since Jul. 2007, the last time I reset the stats:

Messages classified: 135,631,
Classification errors: 910,
Accuracy: 99.32%

That's very impressive result. Thank you POPFile for so many years of service. If you are still using POP3 account, you should try it right now!

BTW, I always hope Gmail can auto label my mails, just like POPFile. Finally we got priority inbox, but that's not enough for me.



tags: , , ,

22:54:43 by fishy - opensource - Permanent Link

no comments yet - no trackbacks yet - karma: 28 [+/-]

Tuesday, March 02, 2010

More tips about my projtags.vim

I wrote a vim plugin named projtags, initially for loading tags file for projects. But as I can "set tags+=tags;" in my vimrc, it's not that useful on the tags file way. I expanded its feature to support commands for per projects, and this is much more useful :P Here are some examples:

Case 1: different coding styles

I did some squid hacking before. My coding style is as below:

set cindent
set autoindent
set smartindent
set tabstop=8
set softtabstop=8
set shiftwidth=8
set noexpandtab
set smarttab
set cino=:0,g0,t0

But squid coding style is different. They use 4 for shiftwidth. As hacking some project, you should always follow the original coding style. But add vim modeline into every file is also unacceptable. So I use projtags.vim to do this job:

let g:ProjTags += [["~/work/squid", ":set sw=4"]]

So that every time I'm editing a file within squid, vim will use 4 instead of 8 for shiftwidth, now I'm happy with both my own codes and squid codes.

Case 2: use ant as makeprg for java projects

First, I use a autocmd to set ant as makeprg for java files:

autocmd BufNewFile,BufRead *.java :setlocal makeprg=ant\ -s\ build.xml

It works well for java files. But as I did some Android development these days, I also have lots of xml files to edit (and then make to see the result). I can't use such a autocmd for xml's, as not all xml's are in a java project. So projtags.vim is again the answer:

let g:ProjTags += [["~/work/pmats", ":set mp=ant\\ -s\\ build.xml"]]

So I can always use ":make" for ant under my Android project now, no matter it's .java or .xml.

My vimrc segment for tags and projtags

set tags+=/usr/local/include/tags
set tags+=/usr/include/tags
set tags+=/opt/local/include/tags
set tags+=tags;

let g:ProjTags = []
let g:ProjTags += [["~/work/squid", ":set sw=4"]]
let g:ProjTags += [["~/work/pmats", ":set mp=ant\\ -s\\ build.xml"]]

Patches for projtags.vim (e.g. Windows support, I haven't tested it but I guess it won't work under Windows now) are welcomed :D You can get the code from the git repo for my various scripts.



tags: , , ,

19:58:01 by fishy - dev - Permanent Link

6 comments - no trackbacks yet - karma: 29 [+/-]

May the Force be with you. RAmen