Code
The best media player money can't buy
19.05.07 11:34 |
Permalink
It turns out that for my needs, the best x-format
media player out there right now is the command-line
version of MPlayer. It plays anything,
is infinitely customisable, has proper support
for subtitles (with live interactive retiming!)
and most importantly, allows me to easily create
the perfect UI for it, tailored exactly to my
needs. In most cases this means "play this file"
or "play this file and use this other file for
the subtitles".
So I created an Automator action (just unpack to ~/Library/Workflows/Applications/Finder) which, after a "Get Selected Finder Items", runs this little AppleScript:
Now I simply select the file(s) in the Finder and Ctrl-click > Automator > Play in MPlayer.
So I created an Automator action (just unpack to ~/Library/Workflows/Applications/Finder) which, after a "Get Selected Finder Items", runs this little AppleScript:
on run {input, parameters}
set args to ""
if length of input > 1 then
repeat with i in input
if name extension of i is in {"avi", "mp4", "ogg", "divx", "mkv"} then
set video to i as string
else
set subs to quoted form of POSIX path of (i as string)
end if
end repeat
set args to " -sub " & subs & " -subfont-text-scale 4"
else
set video to (input as string)
end if
set video to quoted form of POSIX path of video
do shell script "/usr/local/bin/mplayer -framedrop -mc 0.001 -af volnorm=2 " & video & args & " &> /dev/null &"
return true
end run
Now I simply select the file(s) in the Finder and Ctrl-click > Automator > Play in MPlayer.
|
Detecting RSS readers
18.05.07 00:20 |
Permalink
I find it strange that there's no (AFAIK) standard
way to differentiate between a regular web browser
and an RSS aggregator. Ideally, you'd think that the
client requests a certain mimetype so your webapp
could make some decisions. This isn't much of an
issue in most cases, since you just provide an
alternate link the the RSS feed, but I ran into a
problem with authentication.
I wanted RSS users to be able to authenticate using standard HTTP methods, while still giving browser users a nice login window through which they could also reset the password or whatever. The best solution I could come up with thus far is this crude little test that identifies every major browser:
I wanted RSS users to be able to authenticate using standard HTTP methods, while still giving browser users a nice login window through which they could also reset the password or whatever. The best solution I could come up with thus far is this crude little test that identifies every major browser:
$is_browser = preg_match ("/(^Mozilla|^MSIE|^Opera).*/", $_SERVER['HTTP_USER_AGENT']);
Cleaning up your Podcasts
09.05.07 23:25 |
Permalink
I was running out of HD space again and for the heck
of it checked how much space my downloaded podcasts
were taking up - 1.9GB. This wasn't that
surprising, but I did notice there were media files
of podcasts lying around that I had unsubscribed from
a looong time ago. So I needed something that would
do two things:
a) Delete all the podcasts I had already listened to
b) Go through all the residue in the Podcast folder and delete whatever I had already unsubcribed from.
I subscribe to 14 shows so this wouldn't have been much of an issue to do by hand, but I decided to use AppleScript. Here's the script that does all that:
It's not particularly clever or terse and turned out to be quite the overkill, but it works - my Podcasts take up 56 MB now. Plus I can safely check out all the shows I want and not worry about getting rid of them later (just drop the script into ~/Library/Scripts/iTunes). ;-) This could also be easaily improved to, for example, disregard shows that are unselected (if you have a played show that you really want to keep).
If you ask me, iTunes should just re-evaluate your "Keep" preference for podcasts every time you hit "Refresh". That way you could clean things up anytime you like, without resorting to this kind of "violence".
a) Delete all the podcasts I had already listened to
b) Go through all the residue in the Podcast folder and delete whatever I had already unsubcribed from.
I subscribe to 14 shows so this wouldn't have been much of an issue to do by hand, but I decided to use AppleScript. Here's the script that does all that:
set podcastFolder to alias "hd:Users:filipp:Music:iTunes:iTunes Music:Podcasts:"
-- first get rid of played podcasts
tell application "iTunes"
set deleteThese to every file track of playlist "Podcasts" whose unplayed is false
set dontDelete to location of every file track of playlist "Podcasts" whose unplayed is true as list
repeat with thisPodcast in deleteThese
set thePath to (location of thisPodcast as string)
delete thisPodcast
tell application "Finder"
move item thePath to the trash
end tell
end repeat
end tell
-- now comb the fs for any rogue shows
tell application "Finder"
repeat with something in list folder podcastFolder without invisibles
set fullpath to (podcastFolder as string) & something
if dontDelete as string does not contain fullpath then
move item fullpath to the trash
end if
end repeat
end tell
It's not particularly clever or terse and turned out to be quite the overkill, but it works - my Podcasts take up 56 MB now. Plus I can safely check out all the shows I want and not worry about getting rid of them later (just drop the script into ~/Library/Scripts/iTunes). ;-) This could also be easaily improved to, for example, disregard shows that are unselected (if you have a played show that you really want to keep).
If you ask me, iTunes should just re-evaluate your "Keep" preference for podcasts every time you hit "Refresh". That way you could clean things up anytime you like, without resorting to this kind of "violence".
The LEFT OUTER JOIN
07.05.07 00:03 |
Permalink
So let's say you're fetching posts from your blog app
and also want to show the comment count for every
post. You can't use a typical JOIN because some posts
may not have any comments at all and doing a separate
query in the comments table is just plain wasteful.
Use a LEFT OUTER JOIN instead:
SELECT *, COUNT(comments.id) AS comment_count
FROM posts LEFT OUTER JOIN comments ON (posts.id = comments.post_id)
GROUP BY posts.id
ORDER BY date_added DESC
LIMIT 0, 20
A simple build system
11.04.07 14:48 |
Permalink
Nowadays it seems that whenever you're working on
something, you, at some point, have to start
releasing stuff. Typically you can't just take the
directory of your app, compress it and upload
somewhere. And even if you could, doing it manually
would be extremely munday at best.
In a typical webapp project, creating a "build" means: a) cleaning up our configuration of any sensitive information b) removing any data that should not be included in the distribution c) archiving and compressing the build d) id each archive (typically using a timestamp) the build e) uploading the archive somewhere.
Looking back at my previous projects, I think that those steps are pretty universal. So what I want is actually a tool that would do all of those for us. Something non-interactive that we could could be run both on-demand or as part of some scheduling system.
And I'm convinced there are a lot of fancy solutions out there. SVN would probably work (with possibly a bit of hand-coding added), so would probably Ant and a gazillion other SCM systems. Or we could just code one ourselves with a little bit of BASH scripting. This solution is neither particularly elegant nor very easily reusable, but it has worked very well for my current project and handles all the steps I mentioned before. And it doubles very nicely as a little backup scheme.
I chose that timestamp format because it gives me nice granularity, at a stage when fixes and maybe even new features might be added every hour, I can just run this at any time. And the nice thing about this also that it's very flexible, for example the DB schema might be very important in some other project and so you could just drop a mysqldump in there too.
In a typical webapp project, creating a "build" means: a) cleaning up our configuration of any sensitive information b) removing any data that should not be included in the distribution c) archiving and compressing the build d) id each archive (typically using a timestamp) the build e) uploading the archive somewhere.
Looking back at my previous projects, I think that those steps are pretty universal. So what I want is actually a tool that would do all of those for us. Something non-interactive that we could could be run both on-demand or as part of some scheduling system.
And I'm convinced there are a lot of fancy solutions out there. SVN would probably work (with possibly a bit of hand-coding added), so would probably Ant and a gazillion other SCM systems. Or we could just code one ourselves with a little bit of BASH scripting. This solution is neither particularly elegant nor very easily reusable, but it has worked very well for my current project and handles all the steps I mentioned before. And it doubles very nicely as a little backup scheme.
archive=`date +%y%m%d-%H%M-collective`.tar
config='./collective-build/lib/collective.yaml'
echo -n " > Copying..."; rsync -a ./collective/* ./collective-build/ --exclude "data/*/*" && echo " [OK]" && \
echo -n " > Cleaning config..."
sed -e 's/username: [a-z]*/username: username/' -e 's/password: [a-zA-Z]*/password: password/' $config > config.tmp && \
mv config.tmp $config && echo " [OK]"
echo -n " > Creating archive ..."
tar -cf $archive ./collective-build/ && gzip -c $archive > $archive.gz && echo " [OK]"
echo -n " > Uploading $archive.gz..."
curl -u user:pass -T $archive.gz 'ftp://server.com/' && echo " [OK]"
echo -n " > Cleaning up..."
rm -r $archive $archive.gz ./collective-build && echo " [OK]"
exit 0
I chose that timestamp format because it gives me nice granularity, at a stage when fixes and maybe even new features might be added every hour, I can just run this at any time. And the nice thing about this also that it's very flexible, for example the DB schema might be very important in some other project and so you could just drop a mysqldump in there too.
Audio playback with Python
02.04.07 20:00 |
Permalink
I was really surprised there's seemingly no way to do
cross-platform sound output with Python. Looks like you can definitely
do it under Windows, and maybe even on Linux,
but I couldn't find any info about OS X. There's
the rather cryptic mention of Carbon.Snd Sound Manager in
the Python docs but I honestly have no idea what
that is or how to use it.
Anyways, since I (and probably any other Python user on OS X) had PyObjc installed, I decided to use Cocoa to do my sound playback. It turned out to be much easier than I anticipated:
This is for a metronome app and is less than optimal because the wave file can't be played back fast enough (100 and 200 BPM sound the same), but it's a start! More info on using NSSound here.
Anyways, since I (and probably any other Python user on OS X) had PyObjc installed, I decided to use Cocoa to do my sound playback. It turned out to be much easier than I anticipated:
import objc
from AppKit import NSSound
click = NSSound.alloc().initWithContentsOfFile_byReference_("click.wav", True)
click.play()
This is for a metronome app and is less than optimal because the wave file can't be played back fast enough (100 and 200 BPM sound the same), but it's a start! More info on using NSSound here.
Checking for existance
31.03.07 11:30 |
Permalink
There's no built-in for checking if a file exists
when developing Dashboard Widgets in JavaScript. But
with a little help from the BSD subsystem we can do
it easily. For this to work, you obviously need to
have
set in your Info.plist. That opens up a whole world of possibilities. I find that the most effective way is to use file. This way we can even check if something is of the correct type. For example, for a recent UnitInfo update, I had to check if InCrease was installed:
<key>AllowSystem</key>
<true/>
set in your Info.plist. That opens up a whole world of possibilities. I find that the most effective way is to use file. This way we can even check if something is of the correct type. For example, for a recent UnitInfo update, I had to check if InCrease was installed:
foldingBase = "~/Library/InCrease/cpu1/";
var fp = widget.system ('/usr/bin/file -bi ' + foldingBase + 'unitinfo.txt', null).outputString.replace ("\n", "");
if (fp.indexOf ("text/plain") != -1)
{
clientVersion = 'InCrease';
} else {
foldingBase = "~/Library/Folding@Home/";
clientVersion = 'Folding@Home';
}
unitInfoFile = foldingBase + 'unitinfo.txt';
clientConfigFile = foldingBase + 'client.cfg';
Data URL-s
19.03.07 10:25 |
Permalink
I had known about this before, but never really tried
them until yesterday. I needed a really quick way to
post photos from my phone, so decided to write a
little photogallery app. You just dump pictures off
the phone to a certain directory and that's it. In
the process of reading about imagecopyresized, I
stumbled on this method of embedding images:
That's called a data URL and they were first introduced in 1998.
In PHP, I couldn't find a way to read the data directly with imagejpeg(), so I end up overwriting 1 thumbnail for a whole directory of images. Working with images, the PHP memory limitations will quickly come into play. Anyways, here's the whole PHP code that goes through a directory and creates 80x80 px thumnails of a random portion of the picture. You can also see it in action. The source for that script is here.
<img src='data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD' alt='Picture' />
That's called a data URL and they were first introduced in 1998.
In PHP, I couldn't find a way to read the data directly with imagejpeg(), so I end up overwriting 1 thumbnail for a whole directory of images. Working with images, the PHP memory limitations will quickly come into play. Anyways, here's the whole PHP code that goes through a directory and creates 80x80 px thumnails of a random portion of the picture. You can also see it in action. The source for that script is here.
$id = 'data/';
$dh = opendir($id);
while ($f = readdir($dh))
{
$fp = $id . $f;
if (is_file($fp) && $f != 'tmp')
{
list($w, $h) = getimagesize($fp);
$scale = 0.6;
$ns = 80;
$im = imagecreatetruecolor($ns, $ns);
imagecopyresized($im, imagecreatefromjpeg($fp), 0, 0,
rand(0, $w*$scale), rand(0, $h*$scale), $w*$scale, $h*$scale, $w, $h);
imagejpeg($im, 'data/tmp');
echo "
<a href='javascript:show(\"$fp\",$w,$h)' title='" . date('r', filectime($fp)) . "'>
<img src='data:image/jpg;base64," . base64_encode(file_get_contents('data/tmp')) . "' />
</a>";
}
}
with(something)
11.03.07 12:14 |
Permalink
I discovered a nice little shortcut for setting
element properties in JavaScroüt. Instead of saying:
You can "zoom into" the element:
nameDiv = document.createElement('div')
nameDiv.className = 'nameDiv';
nameDiv.innerText = showName;
nameDiv.title = showName;
You can "zoom into" the element:
with (nameDiv = document.createElement('div'))
{
className = 'nameDiv';
innerText = showName;
title = showName;
}
XML stuff
09.03.07 17:35 |
Permalink
A (weird) way to convert PHP arrays to XML. I came up
with this for Collective - passing media metadata
(with getid3) from the server to
JavaScript. It's not supposed to work with
numeric indexes - the algorithm doesn't break,
it just produces invalid XML (can't have numbers
as element names):
A way to convert a file system hierarchy into an XML document, in Python. This is for a simple file-cataloguer I desperately needed (more on this later, hopefully):
function array2xml($array)
{
foreach ($array as $k => $v)
{
if (is_array($v))
$out .= "<$k " . array2xml($v) . "</$k>\n";
else {
$out .= "\n\t$k='$v'";
if (is_array(next($array)) || (array_pop(array_keys($array)) == $k))
$out .= ">\n";
}
}
return $out;
}
A way to convert a file system hierarchy into an XML document, in Python. This is for a simple file-cataloguer I desperately needed (more on this later, hopefully):
imp = minidom.getDOMImplementation()
ldoc = imp.createDocument(None, 'Library', None)
def parsedir(dirname, parent = ldoc.documentElement):
global ldoc
if os.path.isdir(dirname):
newDir = ldoc.createElement('Directory')
newDir.setAttribute('path', dirname)
parent.appendChild(newDir)
parent = newDir
for i in os.listdir(dirname):
fp = os.path.join(dirname, i)
if os.path.isdir(fp):
parsedir(fp, newDir)
else:
newFile = ldoc.createElement('File')
newFile.setAttribute('name', i)
parent.appendChild(newFile)
Punk 1.0.6
08.03.07 13:36 |
Permalink
Released yesterday. The version
number is completely made up. Supports the new
IMDb layout and is rewritten to use
XMLHttpRequest which should also work through
proxies by using the system settings.
XMLHttpRequest seems to also give you caching
for free (at least it creates cruft in the Cache
folder).
This is probably the most succesful thing I've ever made (thanks largely to Martin's nice UI) and this version even got some comments from actual users! 140 downloads in less than 24 hours is not exactly VLC rate, but makes me happy nevertheless. Apple actually featured it again in the Widgets section which always gives you that nice tingly feeling. :)
It's amazing how much energy some feedback gives to a freeware developer. If there's anyone reading this, I would encourage you to send even a small comment or thank-you to your favourite freeware developer(s). It makes all the difference in the world.
This is probably the most succesful thing I've ever made (thanks largely to Martin's nice UI) and this version even got some comments from actual users! 140 downloads in less than 24 hours is not exactly VLC rate, but makes me happy nevertheless. Apple actually featured it again in the Widgets section which always gives you that nice tingly feeling. :)
It's amazing how much energy some feedback gives to a freeware developer. If there's anyone reading this, I would encourage you to send even a small comment or thank-you to your favourite freeware developer(s). It makes all the difference in the world.
Scraping google.com
05.03.07 22:04 |
Permalink
I don't know when this happened, but it seems you
can't scrape (parse) google.com as easily as before.
At least with Python, a simple
will give you this notice instead:
And here's me not understanding why my regexes are not matching. :) Apparently, you should at least show some kind of User Agent. In Python you can do this easily by subclassing URLopener and setting it's version property to something, like:
And I'm really starting to love TextMate. Just type your little Python script and hit Cmd-R. Brilliant.
s=urllib.urlopen('http://www.google.com/search?q=define%3Aesoteric')
r=s.read()
print r
will give you this notice instead:
/snip/ Your client does not have permission to get URL /search?q=define%3Aesoteric from this server /snip/
And here's me not understanding why my regexes are not matching. :) Apparently, you should at least show some kind of User Agent. In Python you can do this easily by subclassing URLopener and setting it's version property to something, like:
class MyOpener(urllib.URLopener):
version = "InternetExploiter/666"
urllib._urlopener = MyOpener()
And I'm really starting to love TextMate. Just type your little Python script and hit Cmd-R. Brilliant.
Good Things pt 2: YAML
09.02.07 19:36 |
Permalink
I got acquinted with YAML during my short run-in
with Ruby on Rails (more on this
some other day, hopefully). Their official
description is:
Absolutely brilliant stuff. What it gives you, is a simple (no joke!) portable data format that's truly human-readable (indentation, baby!) and can easily be parsed into virtually any programming language's native data structure.
For example, in PHP (via the excellent Spyc library), this is how my webapp's DB settings would look like:
Then you just do
And your whole configuration is accessible in a PHP array:
YAML(tm) (rhymes with "camel") is a straightforward machine parsable data serialization format designed for human readability and interaction with scripting languages such as Perl and Python.
Absolutely brilliant stuff. What it gives you, is a simple (no joke!) portable data format that's truly human-readable (indentation, baby!) and can easily be parsed into virtually any programming language's native data structure.
For example, in PHP (via the excellent Spyc library), this is how my webapp's DB settings would look like:
database:
host: localhost
name: collective_development
username: name
password: passwd
type: mysql
charset: utf-8
Then you just do
include 'lib/spyc.php5';
$c = Spyc::YAMLLoad( 'lib/collective.yml' );
And your whole configuration is accessible in a PHP array:
$link = mysql_connect( $c['database']['host'], $c['database']['username'], $c['database']['password'] );
AppleScripting Keynote 3
30.01.07 22:32 |
Permalink
And in particular the add chart command. At first it
seems like a really cool thing - easily create
beautiful charts out of virtually any source. I was
excited to try this with some Webalizer output. Looks
like you have two options - the Automator Action or
Script Editor
The action produces a chart right off the bat, but the input is weird:
What is that? After trying every possible permutation of what I thought an AS 2D array would look like (some of which even compiled!) with different kinds of input sources (AS, text) I finally gave up the Automator Action option.
Using script editor seemed promising at first, but a simple add chart with all the properties produced nothing. Finally managed to find this really nice example, but it only worked with Pages. Digging in the action's bundle revealed that you're supposed to tell the slide to add the chart. OK, time to put this new-found knowledge to work:
The action produces a chart right off the bat, but the input is weird:
Input: (Anything) Two dimensional array of chart labels and data.
What is that? After trying every possible permutation of what I thought an AS 2D array would look like (some of which even compiled!) with different kinds of input sources (AS, text) I finally gave up the Automator Action option.
Using script editor seemed promising at first, but a simple add chart with all the properties produced nothing. Finally managed to find this really nice example, but it only worked with Pages. Digging in the action's bundle revealed that you're supposed to tell the slide to add the chart. OK, time to put this new-found knowledge to work:
tell application "Keynote"
set theData to {{1, 2, 3}, {4, 5, 6}}
set theSlide to (slide 1) of first slideshow
tell theSlide
add chart row names {"Dec", "Jan"} column names {"Machines", "Visits", "Hits"} ¬
data theData type "vertical_bar_3d" group by "column"
end tell
end tell
Tadaa! Sweet. Now all that remains is to add the
webalizer parsing code...
Building universal binaries
24.01.07 15:23 |
Permalink
So far I've had the best success with defining the
following before configure:
Sometimes this lead to:
in which case passing --disable-dependency-tracking to configure seemed to help.
> export LDFLAGS="-Wl,-syslibroot,/Developer/SDKs/MacOSX10.4u.sdk -arch ppc -arch i386"
> export CFLAGS="-isysroot /Developer/SDKs/MacOSX10.4u.sdk -arch i386 -arch ppc"
Sometimes this lead to:
gcc: -E, -S, -save-temps and -M options are not allowed with multiple -arch flags
in which case passing --disable-dependency-tracking to configure seemed to help.
Remembering the QTVR Pan Angle
15.01.07 15:51 |
Permalink
This only works with Safari:
... where getAngle() just uses a cookie:
... which is set with setAngle () like thus:
Finally, the hotspots are wired to go through this function:
QT_WriteOBJECT_XHTML (
'myVRmovie.mov', 800, 600, '',
'controller', 'true',
'id', 'myvrmovie',
'cache', 'true',
'pan', getAngle ());
... where getAngle() just uses a cookie:
function getAngle ()
{
return document.cookie.split ('=')[1];
}
... which is set with setAngle () like thus:
function setAngle ()
{
document.cookie = 'angle=' + Math.floor (document.myvrmovie.GetPanAngle ());
}
Finally, the hotspots are wired to go through this function:
function openPage (url)
{
setAngle ();
window.location (url);
}
Google search to RW (for lazy bums)
30.12.06 11:06 |
Permalink
A really lazy way to add search to
your RW page:
I think you could actually cook up something nice with a Google developer account and XMLHttpRequest...
<div id="gsearch">
<form action="http://www.google.com/search">
<input type="text" name="q"/>
<input type="hidden" name="hl" value="en"/>
<input type="hidden" name="sitesearch" value="http://homepage.mac.com/filipp"/>
<input type="submit" value="Search with Google"/>
</form>
</div>
I think you could actually cook up something nice with a Google developer account and XMLHttpRequest...
Using script.aculo.us with XSLT
28.12.06 20:10 |
Permalink
When using script.aculo.us with XSLT (XML to XHTML),
don't include like this:
This is because scriptaculous.js uses document.write () to include the components and that' apparently verboten when using XSL. The symptoms are weird too:
* Safari will acts as if all was OK, except Ajax.Request won't work
* Firefox/Gecko-based browsers will just hang on loading the document (an invinite "Reading") and you'll notice some class name related errors in the JS log.
In other words, the correct way to include, is by all the files separately and in the right order:
<script src="javascripts/scriptaculous.js" type="text/javascript">
This is because scriptaculous.js uses document.write () to include the components and that' apparently verboten when using XSL. The symptoms are weird too:
* Safari will acts as if all was OK, except Ajax.Request won't work
* Firefox/Gecko-based browsers will just hang on loading the document (an invinite "Reading") and you'll notice some class name related errors in the JS log.
In other words, the correct way to include, is by all the files separately and in the right order:
<script src="javascripts/effects.js" type="text/javascript"></script>
<script src="javascripts/builder.js" type="text/javascript"></script>
<script src="javascripts/dragdrop.js" type="text/javascript"></script>
...
Happy Holidays
23.12.06 00:28 |
Permalink
The past few days have been weird. Got a really great
idea for writing a hwmond replacement called
servermond that would allow you to use at
least some of the functionality of Server Monitor on
any hardware. So far so not good. Getting the SSL
decryption to work to reverse-engineer the Server
Monitor protocol has been unsuccessful due to, quite
frankly, lack of tools. ssldump won't compile
straight, tried the macports version but that won't
run. Finally managed to install ettercap (which seems
like an awesome piece of engineering) but it hasn't
helped me yet... This made me think why there's still
no Mac native front end to any of these powerful free
programs.
Plus, the cigarette addiction's kicking in again. I've been off it for about a year now but the past 5 days I've felt like I quit last weekend or something... Weird.
But to not feel totally unproductive, I've decided to roll out the first public version of Itch - my little Python script for XChat Aqua. Named thus not only because there were a few itches that I wanted to scratch in XChat, but also because it's my first ever Python script (and itchi means "one" in Japanese). Itch features:
There's a few other features that I want to add in there, but the existing ones were "stable" enough to release it into the general public. I consider it my little Christmas present to the Mac IRC population. ;-)
Plus, the cigarette addiction's kicking in again. I've been off it for about a year now but the past 5 days I've felt like I quit last weekend or something... Weird.
But to not feel totally unproductive, I've decided to roll out the first public version of Itch - my little Python script for XChat Aqua. Named thus not only because there were a few itches that I wanted to scratch in XChat, but also because it's my first ever Python script (and itchi means "one" in Japanese). Itch features:
- Simple iTunes and QuickTime Player announcement. /np send nick will try and dcc the current tune to nick.
- /away management (hitting /way toggles you away with a timer) with logging
- Hardware (ioreg) and software (system_profiler) monitoring and specs. Just try /hw and /sp for the possible switches. These are not echoed to the channel.
- iTalk. This was actually pea's brilliant idea. /isay will convert anything you say into "i-talk". iFor iExample iLike iThis.
- Finally, /day will show you what's happened today in history (data from /usr/share/calendar/calendar.*)
There's a few other features that I want to add in there, but the existing ones were "stable" enough to release it into the general public. I consider it my little Christmas present to the Mac IRC population. ;-)
Modulo
19.12.06 11:09 |
Permalink
This one is easy, but so important but used so rarely
that you have time to forget it. How to build a table
programmatically with a certain number of columns,
with only one while - loop:
<table style="border: 1px solid black">
$i = 0;
$col = 4;
$val = 20;
$table = null;
while ($i < $val)
{
$i++;
$table .= '<tr>' + ($i % $col == 0) ? "<td>$i</td></tr>" : "<td>$i</td>";
}
print ($table);
?>
</table>
Variable Scopes
18.12.06 14:44 |
Permalink
value = 0
def setValue():
value = 1
setValue ()
print value
What's that gonna print? That's right - 0. Must use global instead:
value = 0
def setValue():
global value
value = 1
setValue ()
print value
It's actually the exact same with PHP:
$value = 0;
function setValue () { $value = 1; }
setValue ();
print ("value: " . $value);
My brain tells me that if something's been defined previously, then we should be referencing that thing instead. It's not like I'm explicitly using different namespaces in - and outside my function. Like with JavaScript.
Two New Scripts
17.12.06 02:00 |
Permalink
useradd.sh and freplace.sh. The first is a
simple CLI utility for creating users and the
second finds and replaces files.
-a makes new user an administrator.
Replaces target in directory indir with replacement. Has a few extra features as well. Both are still pretty basic, but serve a purpose and I'll probably keep updating them. Handle with care.
> ./useradd.sh
Usage: sudo useradd.sh [-u uid] [-g group] [-a] [-c] [-d home] [-s shell] [-rn realname] name
-a makes new user an administrator.
> ./freplace.sh
Usage: freplace.sh [-sb] [-o owner:group] -d indir target replacement
Replaces target in directory indir with replacement. Has a few extra features as well. Both are still pretty basic, but serve a purpose and I'll probably keep updating them. Handle with care.
Universal Hello World
12.12.06 13:21 |
Permalink
I've been struggling with building universal (fat)
binaries on OS X for quite a while now. Finally
decided to sit down and figure this out. In a
situation with many variables, it's often good to
stop and try to "crystallise" the problem at hand.
What better way to do this than with a Hello World -
if that won't compile and link, then why should
anything else, right?
Then we compile:
So far so good. Now let's see if we can make a fat binary out of this sucker:
Hmm, that wasn't so bad. I guess here is where the "fat" in "fat binary" comes from:
Well, that turned out to be a useless experiment. At least I know that my cross-compiling support works now...
#include <stdio.h>
int main (void)
{
printf ("Hello, world!\n");
return 0;
}
Then we compile:
> gcc hellow.c -o hellow
> ./hellow
Hello, world!
> file hellow
hellow: Mach-O executable ppc
So far so good. Now let's see if we can make a fat binary out of this sucker:
> gcc -isysroot /Developer/SDKs/MacOSX10.4u.sdk -arch ppc -arch i386 -arch ppc64 hellow.c -o hellow-fat
> ./hellow-fat
Hello, world!
> file hellow-fat
hellow-fat: Mach-O fat file with 3 architectures
hellow-fat (for architecture ppc): Mach-O executable ppc
hellow-fat (for architecture i386): Mach-O executable i386
hellow-fat (for architecture ppc64): Mach-O 64-bit executable ppc64
Hmm, that wasn't so bad. I guess here is where the "fat" in "fat binary" comes from:
> ls -lhS hellow*
-rwxr-xr-x 1 filipp filipp 44K Dec 12 13:45 hellow-fat
-rwxr-xr-x 1 filipp filipp 13K Dec 12 13:46 hellow
-rw-r--r-- 1 filipp filipp 80B Dec 12 13:21 hellow.c
Well, that turned out to be a useless experiment. At least I know that my cross-compiling support works now...
BBEdit Session Restore
09.12.06 01:11 |
Permalink
BBEdit's probably my most important tool. Even though
an editor is such a basic thing that it's almost kind
of silly to be attached to one, you just get used to
it over time and it's nice to constantly be finding
new features and ways to use a tool.
I can easily have upwards of 20 documents open simultaneously - sometimes part of the same project, mostly not. This makes you weary of actually closing the app because what if you want to pick up exactly where you left off? Well, luckily, like so many things in life, this problem too can be solved with a little bit of AppleScript:
Just run that after and before every session you want to restore. If you have documents open, it'll save them, if not, open the ones you had open the last time. It's very crude but gets the job done for now. Naturally they don't work with Trasmit links etc because those are just temporarily open.
Properties are wonderful in AS because they are reinitialised only when the script is compiled. That makes them perfect for any kind of "temporary" persistent storage.
I can easily have upwards of 20 documents open simultaneously - sometimes part of the same project, mostly not. This makes you weary of actually closing the app because what if you want to pick up exactly where you left off? Well, luckily, like so many things in life, this problem too can be solved with a little bit of AppleScript:
property theDocuments : {}
tell application "BBEdit"
if (count of document) is greater than 1 then
set theDocuments to file of every document as list
else
open theDocuments
end if
end tell
Just run that after and before every session you want to restore. If you have documents open, it'll save them, if not, open the ones you had open the last time. It's very crude but gets the job done for now. Naturally they don't work with Trasmit links etc because those are just temporarily open.
Properties are wonderful in AS because they are reinitialised only when the script is compiled. That makes them perfect for any kind of "temporary" persistent storage.
PHP's file_get_contents () and cookies
06.12.06 01:16 |
Permalink
Sending cookies - you can do it with
file_get_contents () - just look at the stream
functions' constants (the context parameter). But I
found out it's much easier using the curl plugin
(which isn't always available, btw, but neither is
file_get_contents ()):
It's that simple. An awesome tool for creating all kinds of site parsers.
$c = curl_init ($url);
curl_setopt ($c, CURLOPT_VERBOSE, 1); // For testing
curl_setopt ($c, CURLOPT_COOKIE, "variable=value");
curl_exec ($c);
It's that simple. An awesome tool for creating all kinds of site parsers.
Something New Every Day...
01.12.06 20:32 |
Permalink
JavaScript - Associative Arrays
Considered Harmful
The reason I stumbled across this was that Apple's QuickTime Embedding JS has this problem. The symptoms look crazy - you'll have other JS code inside your embed tag. This happened to me when using Prototype. The fix is easy - just replace "new Array ();" on line 165 with "new Object ();"
The reason I stumbled across this was that Apple's QuickTime Embedding JS has this problem. The symptoms look crazy - you'll have other JS code inside your embed tag. This happened to me when using Prototype. The fix is easy - just replace "new Array ();" on line 165 with "new Object ();"
AppleScript Tidbits
11.11.06 15:23 |
Permalink
AppleScript URL protocol support
Getting the home directory:
Getting and setting the clipboard:
Encoding URLs
Personally, I think you're best off just piping it through PHP:
Bu there's also some info on Apple's website.
How does ScriptEditor know which app is scriptable?
By looking for the NSAppleScriptEnabled key in an application bundle's Info.plist
Getting the home directory:
set theHomeDir to the POSIX path of home directory of (system info)
Getting and setting the clipboard:
set whatever to the clipboard
set the clipboard to "whatever"
Encoding URLs
Personally, I think you're best off just piping it through PHP:
do shell script "echo myurl | /usr/bin/php -r \"urlencode(fgets(STDIN));\""
Bu there's also some info on Apple's website.
How does ScriptEditor know which app is scriptable?
By looking for the NSAppleScriptEnabled key in an application bundle's Info.plist
Automate Out-of-Office Reply Toggling
04.11.06 16:43 |
Permalink
This one actually made it to the site:
If there's one thing that computers are better at than humans, it's remembering things. Take for example the typical Out Of Office email reply - you go on vacation and set a rule in Mail.app to automatically respond of your absence to any email with a certain criteria. Then you come back and a day or two later remember to turn the notification back off again.
Well if You use iCal, your Mac most likely already knows when you're leaving and coming back so let's simply tie that information with Mail.app. Here's how:
Open Automator and from the Automator library add a "Run AppleScript" step.
Replace the code with the following, putting in the name of your Out of Office rule:
3) File > Save As Plug-In, give it a name (like "Toggle Out-Of-Office"), Plug-in for: iCal Alarm. Save.
iCal will open with a new event which you can simply delete. Then just create events on the start and end dates of your vacation (if you haven't already) and set our newly created script as the alarm (which you'll find in the alarm "Open file" dropdown menu).
The reply will now activate when yo leave and deactivate when you return. Just make sure your rule is Inactive before you go. :)
If there's one thing that computers are better at than humans, it's remembering things. Take for example the typical Out Of Office email reply - you go on vacation and set a rule in Mail.app to automatically respond of your absence to any email with a certain criteria. Then you come back and a day or two later remember to turn the notification back off again.
Well if You use iCal, your Mac most likely already knows when you're leaving and coming back so let's simply tie that information with Mail.app. Here's how:
Open Automator and from the Automator library add a "Run AppleScript" step.
Replace the code with the following, putting in the name of your Out of Office rule:
set myRule to "Name of my Out of Office rule"
tell application "Mail"
set enabled of rule myRule to not enabled of rule myRule
end tell
3) File > Save As Plug-In, give it a name (like "Toggle Out-Of-Office"), Plug-in for: iCal Alarm. Save.
iCal will open with a new event which you can simply delete. Then just create events on the start and end dates of your vacation (if you haven't already) and set our newly created script as the alarm (which you'll find in the alarm "Open file" dropdown menu).
The reply will now activate when yo leave and deactivate when you return. Just make sure your rule is Inactive before you go. :)
Flannel 1.0
03.11.06 12:02 |
Permalink
This isn't much of a release announcement since it's
not even available for download yet, but Flannel's
demo page is finally up now.
This is all part of a not-so-elaborate scheme to get Flannel out there in the hands of the users. Not knowing the current state of true WYSIWYG publishers out there, I still think it could be very useful for people who just want their stuff online quickly and easily.
There's still alot of work to be done and the version that's running the demo's not the one I had in mind for the 1.0 release, but still...
This is all part of a not-so-elaborate scheme to get Flannel out there in the hands of the users. Not knowing the current state of true WYSIWYG publishers out there, I still think it could be very useful for people who just want their stuff online quickly and easily.
There's still alot of work to be done and the version that's running the demo's not the one I had in mind for the 1.0 release, but still...
Search & Replace with nano!
17.09.06 21:19 |
Permalink
A Random Quote For Site Slogan
09.09.06 12:28 |
Permalink
window.onload = function()
{
var quotes = ["I wanna be a racecar passenger.", "Alright, you're a cook - can you farm?"];
var i = Math.floor(Math.random()*quotes.length);
document.getElementsByTagName('h2')[0].innerHTML = '"' + quotes[i] + '"';
}
Don't forget to add blank lines before and after the JS!
A Teacher's Pet
08.09.06 11:04 |
Permalink
Punk made it to the Staff Favorites list. It will
also be included on the cover CD of Univers Mac, a French Mac
Magazine! Thanks!
sortUsingSelector
05.09.06 10:09 |
Permalink
After spending 4 days with the problem of sorting an
array of NSDictionaries (!!) using every kind of
method imaginable, I finally found the solution:
You can't use sortUsingSelector because that operates on the object in the array (in this case an NSDictionary). That explains the countless ("[CFDictionary compareScores] selector not recognized etc...") I was getting. Phew
So use sortUsingFunction instead. And as the guys at cocoadev so eloquently showed:
Where "context" is the NSDictionary key you want to sort by.
You can't use sortUsingSelector because that operates on the object in the array (in this case an NSDictionary). That explains the countless ("[CFDictionary compareScores] selector not recognized etc...") I was getting. Phew
So use sortUsingFunction instead. And as the guys at cocoadev so eloquently showed:
int someSort (id obj1, id obj2, void *context)
{
return [[(NSDictionary *)obj1 objectForKey:(NSString *)context] compare:[(NSDictionary *)obj2 objectForKey:(NSString *)context]];
}
Where "context" is the NSDictionary key you want to sort by.
Getting Stuff Into MySQL
14.08.06 19:29 |
Permalink
Forget clumsy scripts to load SQL files into MySQL.
Here's how it worked for me:
check your php.ini "upload_max_filesize"
Strangely, MySQL 4.x doesn't like the "ENCLOSED BY" statement (it works, but will only import the first row)
check your php.ini "upload_max_filesize"
$newFile = move_uploaded file ($FILES['yourfile']['tmp_name'], '/var/tmp/yourfile');
$sql = "LOAD DATA INFILE '$newFile'
INTO TABLE db.table
FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\"' LINES TERMINATED BY '\r'";
Strangely, MySQL 4.x doesn't like the "ENCLOSED BY" statement (it works, but will only import the first row)
PHP weirdness
01.06.06 07:50 |
Permalink
Symbian madness
26.04.06 07:51 |
Permalink
More On Spelling
23.03.06 14:00 |
Permalink
phpBB privmsgs to PunBB
19.02.06 14:01 |
Permalink
DELETE FROM punmessages;
INSERT INTO punmessages (subject,sender_id, sender, owner, posted, message, sender_ip,smileys,showed)
(SELECT privmsgs_subject, privmsgs_from_userid,
(SELECT username FROM phpbb_users WHERE phpbb_users.user_id = privmsgs_from_userid),
privmsgs_to_userid, privmsgs_date, privmsgs_text, INET_NTOA(CONV(privmsgs_ip,16,10)), privmsgs_enable_smilies,
(SELECT user_new_privmsg FROM phpbb_users WHERE phpbb_users.user_id = privmsgs_from_userid)
FROM phpbb_privmsgs, phpbb_privmsgs_text
WHERE privmsgs_id = privmsgs_text_id);