tag:blogger.com,1999:blog-46513290239186550722024-03-12T23:10:05.570-03:00Sarah HappyAnonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.comBlogger142125tag:blogger.com,1999:blog-4651329023918655072.post-9083758381531344262016-03-15T18:21:00.002-03:002016-03-15T18:23:44.492-03:00my toothbrush's homeMy toothbrush lives in a tube in my everyday bag, and like most of my stuff is also rainbow.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihfAsRZjYRmJaIlnSm_t2HVAdbhJNHz4CwcqT7atl-e17GWWAyD_kJpu14MBa614bdJIHHVMYAotUospMtpuNgX4Av1JSBvDL1oWdhA8oVp-7VMhsupe3G1MpFoYCXpnw5mCvsHBg0SGEy/s1600/toothbrush.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihfAsRZjYRmJaIlnSm_t2HVAdbhJNHz4CwcqT7atl-e17GWWAyD_kJpu14MBa614bdJIHHVMYAotUospMtpuNgX4Av1JSBvDL1oWdhA8oVp-7VMhsupe3G1MpFoYCXpnw5mCvsHBg0SGEy/s320/toothbrush.jpg" width="320" /></a></div>
This is actually the second version of my toothbrush tube. The previous version was wrapped in painted hockey tape. It got really dirty after a while, so it had to go.<br />
<br />
Now the tube is covered in acrylic paint and Mod Podge, which is basically white glue, leaving a plastic finish.<br />
<br />
Having something looking like this got the attention from greyhound security when they searched my carry on bag. This is a thing Greyhound does now, they check carry on bags for weapons and stuff, and make everyone empty their pockets, at the occasional terminal. I got to see quite a look of surprise on the security person's face when they opened the tube and found a toothbrush.<br />
<br />Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-45292747790525787672016-02-03T20:05:00.001-04:002016-02-03T20:06:24.221-04:00"The nest has fridge poetry"<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKmQQ0ooC-wR9CLTj4eXMdAjHtXqXnvY8UEt778adDCCyg-fqovtTwtvE4j225sdQOBzNWamDTa-N4iuVAI2uVNFArUubuIBrgM5OvG8PBjijY3-ikr8fzIwhjNgTqdaCJubF7DX2GZ5kc/s1600/P1060052.JPG" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKmQQ0ooC-wR9CLTj4eXMdAjHtXqXnvY8UEt778adDCCyg-fqovtTwtvE4j225sdQOBzNWamDTa-N4iuVAI2uVNFArUubuIBrgM5OvG8PBjijY3-ikr8fzIwhjNgTqdaCJubF7DX2GZ5kc/s320/P1060052.JPG" width="320" /></a>"The nest has fridge poetry" has been on the wall for several months.<br />
<br />
The last person I saw with some on her fridge said that she had made it themselves, I think that was the story.<br />
<br />
Today I went to Staples up the hill and picked up a package of magnetic paper, there were four sheets in the pack. When I got back to the nest a quick Google search found <a href="http://tim.cexx.org/?p=459">do it yourself magnetic fridge poetry</a> directions.<br />
<br />
The parts:<br />
- words<br />
- word processor<br />
- magnetic paper<br />
- injet printer<br />
- sissors<br />
<br />
I took the words from the <a href="http://jbauman.com/gsl.html">general service list</a>, as the directions suggested. Put the text of the word list in a text editor, cut the extra bits out of the text leaving only a list of words one per line. Took that list and joined the lines together separated by three spaces resulting in a long spaced out line. Put that long line in OpenOffice writer, set the font to "Verdana" 12pt and collapsed the margins. Printed the first page. And cut up the magnet with scissors.<br />
<br />
I still have two and a bit sheets of magnetic paper, I think I will do an analysis of one of my programming projects and find the most common words and symbols and make a sheet from that. Programmer fridge poetry, is that a thing yet? I can find <a href="http://magneticpoetry.com/products/geek">Geek Fridge Poetry</a>, but none for Java. Interesting.<br />
<br />Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-45641823153137169532016-02-01T12:49:00.000-04:002016-02-01T12:54:02.058-04:00Flour on the counter<div class="separator" style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMDDYu-L4YtUKmJF3bq-0vZFw-0CStRX3LOTpm33_gOWMOq4xIheZi-8uKIpwhLeF7urem-k9oUTsv7F7vIA9vMv_fElnOJP8TEw1AMagU7loXD2IffCGlK2n4_3ZOsm04zHVQEevj5ILJ/s1600/P1060040.JPG" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMDDYu-L4YtUKmJF3bq-0vZFw-0CStRX3LOTpm33_gOWMOq4xIheZi-8uKIpwhLeF7urem-k9oUTsv7F7vIA9vMv_fElnOJP8TEw1AMagU7loXD2IffCGlK2n4_3ZOsm04zHVQEevj5ILJ/s320/P1060040.JPG" width="320" /></a>I had an empty large container that formerly contained ground coffee, and wanted to put flour on the counter for easier access when making bread. After cleaning the container out and drying it well it worked well. Later I had an empty flour bag and decided to make a label from it. Even later I was at a Dollarama with Cassandra and she pointed out some rainbow wrapping paper and mentioned that I can cover things with it. With much gratitude I covered the silver container with Mod Podge and wrapping paper, another layer of Mod Podge and the label, and another over top of the label to seal it all down. </div>
Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-25962682364514138412015-11-11T13:59:00.000-04:002015-11-11T14:14:05.901-04:00The nest has draft guardsThe nest where I live is drafty, and the weather is getting cold again. Last year I tried foam tape around the doors with the effect of making the door hard to close and still drafty. The nest didn't get above 15c in the depth of winter, so I set the kitchen temperature to 10c and left the rest off. The power bill was $150/month.<br />
<br />
Last spring I went on a mission to get a bunch of my stuff from storage in Halifax, including a big bag of worn out clothes and fabric scraps.<br />
<br />
Last week I cut up the toes off some old socks and sewed them end to end making longer socks. I took these socks and stuffed them with more worn out clothes. Finally I took these stuffed socks and tacked them to the drafty doors of the nest.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga2c9IKZkwH4RiIqH1qHRmfA_himdyp2BxB_g14B3vR_gYFIdK6GmC2X2jDifW92CvtMQmevD0HgpIM2M6ppXpZ6pFwkvKh-_e-zZ8RuG1r0TdmNIlcyhQh-lMpLHBaKLZJntfrLSnFckT/s1600/P1040924.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga2c9IKZkwH4RiIqH1qHRmfA_himdyp2BxB_g14B3vR_gYFIdK6GmC2X2jDifW92CvtMQmevD0HgpIM2M6ppXpZ6pFwkvKh-_e-zZ8RuG1r0TdmNIlcyhQh-lMpLHBaKLZJntfrLSnFckT/s320/P1040924.JPG" width="240" /></a></div>
The outside door looses a lot of heat on the opening edge, and later I may do the same to the bottom of that door too. It turns out that the metal front door is wood on the narrow edges.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXkLZNwnAr97UTYAHJUyfD5ReSSDy6KPZu8S3axSnSoFKlfXon4Gcdd0jLTyRXMiS0ZeEnAiUa4rDsgrbL9tLLvU3X48b2jRD4pc_bP-OYNqMKdc4APw4ig80JvQwVaHaQcBQ50eCIlroi/s1600/P1040925.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXkLZNwnAr97UTYAHJUyfD5ReSSDy6KPZu8S3axSnSoFKlfXon4Gcdd0jLTyRXMiS0ZeEnAiUa4rDsgrbL9tLLvU3X48b2jRD4pc_bP-OYNqMKdc4APw4ig80JvQwVaHaQcBQ50eCIlroi/s320/P1040925.JPG" width="320" /></a></div>
Also, the bottom room gets below zero in the winter too, and has a big gap at the bottom of the door.<br />
<br />
Both doors work as they usually do.<br />
<br />
Over the next few months I get to see how effective this is.<br />
<br />Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com2tag:blogger.com,1999:blog-4651329023918655072.post-72594356176200306702015-10-28T17:27:00.003-03:002015-10-28T18:47:22.604-03:00Happy's memory card holderLong ago I made a memory card holder that held three cards. It traveled with me across the country at least twice, maybe all three times, and worked well to keep my memory cards from getting lost. Recently, after seeing the card holder I made for Ep1c I wanted one that looked like that for myself. So, I took the one I
already had, took it apart, cut some more parts, painted it like a rainbow
sandwich, and sewed it up into a memory card holder that holds six cards.<br />
<br />
This is something I can be commissioned to make, say fifty dollar painted, or twenty unpainted?<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQ2XK8SJdHd5274Hkt-gzAlSyQf1ey6IBh0-NhWIjal3f-AlatgwYx51EhlZicD0rsT_A7U8Vl27G3vwiqOfIj3eR4R_OpAPJDGk-1ijrVcCMt7auMtpljknGJfnceYcwUd84TNATUdPoE/s1600/P1030998.JPG" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQ2XK8SJdHd5274Hkt-gzAlSyQf1ey6IBh0-NhWIjal3f-AlatgwYx51EhlZicD0rsT_A7U8Vl27G3vwiqOfIj3eR4R_OpAPJDGk-1ijrVcCMt7auMtpljknGJfnceYcwUd84TNATUdPoE/s320/P1030998.JPG" /></a><br />
<br />Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-87758974139497818452014-01-10T11:27:00.000-04:002014-01-10T11:27:44.718-04:00Gmail Inbox Notification WidgetI wanted to display my Gmail Inbox unread message count on my personal web page on Google Sites, so I made a tiny Google Apps Script widget to do this.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOx4UerZFvdEIMUMMQ7dLXoUz6ASiUG3v_uFfBzh3ruKoH32s9BUuZPLXCHopyYjPY7zPVWMmbdv8WPwAEImgewg_nhs1LOj3W1tKk3OKEz3x_qVxfGoERS6No-wnvDXPGhgWeaShrA__9/s1600/Screen+shot+2014-01-10+at+11.19.15+AM.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOx4UerZFvdEIMUMMQ7dLXoUz6ASiUG3v_uFfBzh3ruKoH32s9BUuZPLXCHopyYjPY7zPVWMmbdv8WPwAEImgewg_nhs1LOj3W1tKk3OKEz3x_qVxfGoERS6No-wnvDXPGhgWeaShrA__9/s1600/Screen+shot+2014-01-10+at+11.19.15+AM.png" /></a><br />
<br />
The code turned out very simple.<br />
<br />
<tt>code.gs</tt>:<br />
<pre class="prettyprint">function doGet() {
return HtmlService.createTemplateFromFile('page')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.NATIVE);
}
function getInboxUnreadCount() {
return GmailApp.getInboxUnreadCount();
}
</pre><br />
<tt>page.html</tt>:<br />
<pre class="prettyprint"><style type="text/css">
#refresh { color: blue; text-decoration: underline; }
#refresh:hover { cursor: pointer; }
</style>
<div id="inbox">
Inbox (<span id="count">...</span>)
<span id="refresh">refresh</span>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
function check() {
clearTimeout(check.timer);
google.script.run
.withSuccessHandler(function(result) {
$('#count').text(result);
$('#inbox').toggleClass('unread', result > 0);
})
.getInboxUnreadCount();
$('#count').text("...");
check.timer = setTimeout(check, 600000);
}
$(function() {
check();
$('#refresh').click(check);
});
</script></pre><br />
The <tt>getInboxUnreadCount()</tt> function needed to be run from the script editor to grant the needed permissions to the script, after that it worked from my personal home page.<br />
Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-3045047958335934442014-01-10T07:12:00.001-04:002014-01-10T09:56:39.370-04:00Interactive Javascript DevelopmentWhen I was traveling to a friend's place to give her a Christmas not alone I got picked up by someone who suggested I learn jQuery... so I did, and in the process wrote a JavaScript Read Eval Print Loop that makes jQuery and JavaScript development as easy as interactive Python or BASH programming.<br />
<br />
The result is a single file web application, <a href="https://googledrive.com/host/0BwPL-62FoB5Id1dWTTJSS2ZaVzg/javascript-repl.html">JavaScript Read, Eval, Print, Loop</a>. View the source to see what was needed to make it work.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaZ97edVWE9-rYPQE8dSCTQsl1nvMDDOFug1rVsyRdvOySpfhtw-YIR7qH_Wqxu96QI-oacfUL8gs7zKV7onzgkX6dQGfEJwIQUPV4iJQC2eRfp6oAJppYdyuAiL6aPB66G4ieKQqoWdoZ/s1600/Screen+shot+2014-01-10+at+9.54.15+AM.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaZ97edVWE9-rYPQE8dSCTQsl1nvMDDOFug1rVsyRdvOySpfhtw-YIR7qH_Wqxu96QI-oacfUL8gs7zKV7onzgkX6dQGfEJwIQUPV4iJQC2eRfp6oAJppYdyuAiL6aPB66G4ieKQqoWdoZ/s320/Screen+shot+2014-01-10+at+9.54.15+AM.png" height="225" width="640" /></a>Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-69696059578794600342014-01-06T12:23:00.000-04:002014-01-10T11:16:25.094-04:00Facebook notification widgetI wanted to display my facebook notification information on my personal home screen.<br />
<br />
I ended up with a single HTML file which I have hosted on Google Drive and embedded in an iframe in my personal home screen on Google Sites.<br />
<br />
The end result is a single line where the numbers change if there are any notifications to report.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuZzEyj7BRezyr__mUvk1LLs2KXRjvBPOok-d7HxdAqQDpUR2IAEuCHxWCpjf95IiP5jl_Kso4ZsK7Qbd3HZED8dwH5ohZAZRhC4rQFIGKWGjr58kDDxL5wfI0fb3HQ9iNCLTtRtFhV5_i/s1600/Screen+shot+2014-01-10+at+11.15.47+AM.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuZzEyj7BRezyr__mUvk1LLs2KXRjvBPOok-d7HxdAqQDpUR2IAEuCHxWCpjf95IiP5jl_Kso4ZsK7Qbd3HZED8dwH5ohZAZRhC4rQFIGKWGjr58kDDxL5wfI0fb3HQ9iNCLTtRtFhV5_i/s1600/Screen+shot+2014-01-10+at+11.15.47+AM.png" /></a><br />
<br />
<pre class="prettyprint"><html><head><title>Facebook Quick Check</title></head><body>
<style type="text/css">
body { margin: 0px; }
</style>
<div id="fb-root"></div>
<div>
<span id="login"><a href="javascript:doLogin()">FB Login</a>: </span>
Notice (<span id="notice">...</span>)
Friend (<span id="friend">...</span>)
Inbox (<span id="inbox">...</span>)
<a href="javascript:loadStatus()">refresh</a>
</div>
<div id="out"></div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
</pre>I wanted a fluent API that felt like Google Apps Script server calls instead of the regular Facebook API, so I made a quick one...<br />
<pre class="prettyprint">function FBRun(parent) {
this.successHandler = parent ? parent.successHandler : function(result) { };
this.errorHandler = parent ? parent.errorHandler : function(result) { };
};
FBRun.prototype.withSuccessHandler = function(handler) {
var obj = new FBRun(this);
obj.successHandler = handler;
return obj;
};
FBRun.prototype.withErrorHandler = function(handler) {
var obj = new FBRun(this);
obj.errorHandler = handler;
return obj;
};
FBRun.prototype.getHandler = function() {
var obj = this;
return function(response) {
if(!response || response.error) {
return obj.errorHandler(response);
}
return obj.successHandler(response);
}
};
FBRun.prototype.get = function(url, data) {
FB.api(url, 'get', data, this.getHandler());
return this;
};
FBRun.prototype.login = function(data) {
FB.login(this.getHandler(), data);
return this;
}
FBRun.prototype.loginStatus = function() {
FB.getLoginStatus(this.getHandler());
return this;
}
fbrun = new FBRun();
</pre>Load the Facebook API and check the login status on success...<br />
<pre class="prettyprint">$(document).ready(function() {
$.ajaxSetup({ cache: true });
$.getScript('//connect.facebook.net/en_UK/all.js', function(){
FB.init({
appId: 'APPLICATION_KEY',
});
fbrun
.withSuccessHandler(onLogin)
.loginStatus();
});
});
</pre>The code for the login link...<br />
<pre class="prettyprint">function doLogin() {
fbrun
.withErrorHandler(display)
.withSuccessHandler(onLogin)
.login({scope: 'manage_notifications,read_requests,read_mailbox'});
}
var perms = {};
function onLogin(response) {
if(response.status === 'connected') {
fbrun
.withErrorHandler(display)
.withSuccessHandler(function(response) {
perms = response.data[0];
if(perms.manage_notifications && perms.read_requests && perms.read_mailbox) {
$('#login').hide();
}
start();
})
.get('/me/permissions');
}
}
function display(response) {
$('#out').text(JSON.stringify(response));
}
</pre>Load the information and start the refresh timer...<br />
<pre class="prettyprint">function start() {
loadStatus();
if(start.active) {
return;
}
start.active = true;
function check() {
loadStatus();
setTimeout(check, 600000);
}
setTimeout(check, 600000);
}
function loadStatus() {
if(perms.manage_notifications) {
$('#notice').text('...');
fbrun
.withErrorHandler(display)
.withSuccessHandler(function(response) {
if(response.summary.unseen_count != undefined) {
$('#notice').text(response.summary.unseen_count);
} else {
$('#notice').text("0");
}
})
.get('/me/notifications', { limit: 0 });
} else {
$('#notice').text('-');
}
if(perms.read_requests) {
$('#friend').text('...');
fbrun
.withErrorHandler(display)
.withSuccessHandler(function(response) {
$('#friend').text(response.summary.unread_count);
})
.get('/me/friendrequests', { limit: 0 });
} else {
$('#friend').text('-');
}
if(perms.read_mailbox) {
$('#inbox').text('...');
fbrun
.withErrorHandler(display)
.withSuccessHandler(function(response) {
$('#inbox').text(response.summary.unseen_count);
})
.get('/me/inbox', { limit: 0 });
} else {
$('#inbox').text('-');
}
}
</script>
</body></html>
</pre><br />
Update: limit the data returned to just the summaries by setting the item limit to zero.Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-71903482671372855472014-01-04T12:19:00.005-04:002014-01-04T12:22:19.060-04:00Google Apps Script execution modelI was wondering if <a href="https://developers.google.com/apps-script/">Google Apps Script</a> web applications retained state between invocations, so I did a small experiment.<br />
<br />
Using a Script created in Google Drive:<br />
<br />
<tt>Code.gs</tt>:<br />
<pre class="prettyprint">function doGet() {
return HtmlService.createTemplateFromFile('page')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.NATIVE);
}
var date = new Date();
var last = -1;
function func(value) {
Utilities.sleep(100);
var out = "date = " + date + ", last = " + last;
last = value;
return out;
}
</pre><br />
<tt>page.html</tt>:<br />
<pre class="prettyprint"><style type="text/css">
pre {
border: 1px solid green;
}
</style>
<div id="out">
<pre>loading...</pre>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
$(function() {
for(var i = 0; i < 2; i++) {
launch(i);
}
});
setTimeout(function() {
for(var i = 2; i < 4; i++) {
launch(i);
}
}, 2000);
function launch(id) {
$("<pre />").text("started " + id).appendTo('#out');
google.script.run
.withSuccessHandler(function(result) {
$("<pre />").text("finished " + id + ": " + result).appendTo('#out');
})
.func(id);
}
</script>
</pre><br />
This output was produced:<br />
<pre>loading...
started 0
started 1
finished 0: date = Sat Jan 04 2014 12:03:06 GMT-0400 (AST), last = -1
finished 1: date = Sat Jan 04 2014 12:03:06 GMT-0400 (AST), last = -1
started 2
started 3
finished 2: date = Sat Jan 04 2014 12:03:08 GMT-0400 (AST), last = -1
finished 3: date = Sat Jan 04 2014 12:03:08 GMT-0400 (AST), last = -1
</pre><br />
Which leads me to the conclusion that the server is stateless, at least from the point of view of the scripts running on it.<br />
Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com1tag:blogger.com,1999:blog-4651329023918655072.post-76372091075621895932014-01-03T07:17:00.000-04:002014-01-03T07:57:36.540-04:00lazy initialization in JavaScriptI have a function that is not often used and has a long initialization step, when it is used it is used often, making it a very good candidate for lazy initialization. This function is in a Google Apps Script web application, so minimizing processing time is desirable.<br />
<br />
Functions in JavaScript are assignable closures, so it is easy to write a lazy initializing function without any if statements.<br />
<br />
<pre class="prettyprint">var getImage = function(name) {
var images = { };
var files = MESSAGES_FOLDER.getFiles();
while(files.hasNext()) {
var file = files.next();
images[file.getName()] = file.getDownloadUrl();
}
return (getImage = function(name) {
return images[name];
})(name);
}
</pre><br />
After the initialization part of the function (making a map of file names to download urls) it is replaced by a new function that does not include the initialization, and that new function is called to get the result for the first use.<br />
<br />
This is also an implementation of the <a href="http://en.wikipedia.org/wiki/State_pattern">State pattern</a>.Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com2tag:blogger.com,1999:blog-4651329023918655072.post-49121924244180357442013-12-05T10:35:00.000-04:002013-12-05T11:18:11.632-04:00Simple and super practical note bookAll the notebooks I have kept in my bag so far seemed lacking in little ways: I couldn't easily tuck things into them; sometimes finding the next blank page was a challenge; they consume a lot more space than I feel they need to; taking pages out of them quickly became unsightly.<br />
<br />
So I started pondering how to make one where I could freely add and remove pages without having an effect on the feel of the note book.<br />
<br />
I made the notebook from plastic canvas, and sewed it with strips of shopping bags. The shell does not care about getting wet, but the pages inside do.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUUPzSSSw7tF8rLZG3xwjzFMzcjH7HIr9lJkxGFullg5N9i7-84Fm9IzI4UThmVUUguM3ddLRd7Wt1jBh8PIIQvobnRO1UtmWZUyEra26E5A0jsLZxuAW_bGsW0xwKEl_QiH7OEos-0Pu1/s1600/131205_071547.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUUPzSSSw7tF8rLZG3xwjzFMzcjH7HIr9lJkxGFullg5N9i7-84Fm9IzI4UThmVUUguM3ddLRd7Wt1jBh8PIIQvobnRO1UtmWZUyEra26E5A0jsLZxuAW_bGsW0xwKEl_QiH7OEos-0Pu1/s320/131205_071547.jpg" width="320" /></a></div>
<br />
It is just larger than a quarter sheet of letter sized paper, so it is very easy to make new pages as I need them.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuvdO86qeS_iAzx_sidI-M0AFHDBIyJphWSfsm-f7V8Xg1fXXGM4FXxZuc2m9IenIS7twY6oD00fSH0uTRgHz7yAlrmrUHtopW1QicMfR8DqRZlR5IEUeit5b1MjEUW5jGvHxVQqhewX6l/s1600/131205_071604.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuvdO86qeS_iAzx_sidI-M0AFHDBIyJphWSfsm-f7V8Xg1fXXGM4FXxZuc2m9IenIS7twY6oD00fSH0uTRgHz7yAlrmrUHtopW1QicMfR8DqRZlR5IEUeit5b1MjEUW5jGvHxVQqhewX6l/s320/131205_071604.jpg" width="320" /></a></div>
<br />
The pages are folded over a strip of plastic, so they can be completely rearranged, and anything thin that can be folded can be tucked between any of the pages. I did not realize in advance that I can always be writing on the top few pages by just moving blank pages up to the top of the stack. I had printed driving directions on the left side earlier in the week, and now they have been flipped over to be reused as note pages.<br />
<br />
In the future I am going to try some graph paper or lined paper in here, but this is also working very well.Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-368697334459515512013-11-21T16:58:00.001-04:002013-11-21T17:36:19.341-04:00Swing Components in HTML GUI in JavaI would like to use Swing components in a <a href="/2013/11/first-html-gui-in-java.html">HTML based layout</a>. Initially I tried to have empty elements with id attributes in the loaded text, but they were pruned from the internal representation, so I put text inside of them and replaced the whole element with the Swing component.<br />
<br />
<tt>HtmlGui.java</tt>:<br />
<pre class="prettyprint lang-java">package ca.sarah_happy.sandbox;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.html.HTMLDocument;
public class HtmlGui implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new HtmlGui());
}
private JTextPane viewer;
private HTMLDocument document;
@Override
public void run() {
try {
viewer = new JTextPane();
viewer.setEditable(false);
viewer.setPreferredSize(new Dimension(400, 300));
JFrame frame = new JFrame("screen");
frame.setContentPane(new JScrollPane(viewer));
frame.pack();
frame.setLocationByPlatform(true);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
URL screen = HtmlGui.class.getResource("screen.html");
viewer.setPage(screen);
document = (HTMLDocument) viewer.getDocument();
viewer.addPropertyChangeListener("page", onLoad);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
private JSlider slider;
private PropertyChangeListener onLoad = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent e) {
JButton button = new JButton("Button");
button.addActionListener(onButton);
insertComponent("button", button);
slider = new JSlider();
slider.addChangeListener(onSlider);
insertComponent("slider", slider);
}
};
private ChangeListener onSlider = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
addOutput("changed slider: " + slider.getValue());
}
};
private void insertComponent(String id, Component component) {
Element e = document.getElement(id);
viewer.setCaretPosition(e.getStartOffset());
viewer.moveCaretPosition(e.getEndOffset());
viewer.insertComponent(component);
}
private ActionListener onButton = new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
addOutput("pressed button");
}
};
private void addOutput(String text) {
try {
Element out = document.getElement("output");
document.insertString(out.getEndOffset() - 1, text + "\n", null);
} catch (BadLocationException ex) {
ex.printStackTrace();
}
}
}
</pre><br />
<tt>screen.html</tt>:<br />
<pre class="prettyprint lang-html"><html><body>
Some stuff...
<p><span id="button">button</span> <span id="slider">slider</span></p>
<p><i>Output:</i>
<div id="output"></div></p>
</body></html>
</pre><br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOQGdfpl6i_Ae0EhIwYoglB_wvsY4yUVOLUwWfsAyLn8E5G59z1phNZ6jv69Fjgjl_b2QbWhpdbGAacpEaRZgq3jDY9lviEPgkd1RRgtj1wKEN6G1vnKK6MLnImJ2nHX2S-v-9HJGtboEe/s1600/Screen+shot+2013-11-21+at+4.54.13+PM.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOQGdfpl6i_Ae0EhIwYoglB_wvsY4yUVOLUwWfsAyLn8E5G59z1phNZ6jv69Fjgjl_b2QbWhpdbGAacpEaRZgq3jDY9lviEPgkd1RRgtj1wKEN6G1vnKK6MLnImJ2nHX2S-v-9HJGtboEe/s320/Screen+shot+2013-11-21+at+4.54.13+PM.png" /></a>Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-72044228267390499542013-11-21T13:39:00.003-04:002013-11-21T13:48:47.065-04:00first HTML GUI in JavaJava GUI layout can be cumbersome, and HTML layout is less cumbersome. I wanted to have a screen in a Java application based on a HTML document.<br />
<br />
For an initial attempt I made a screen with two buttons and an output area using a JEditorPane, the buttons are made from links and the output area is made from a document element. <br />
<br />
<tt>HtmlText.java</tt>:<br />
<pre class="prettyprint lang-java">package ca.sarah_happy.sandbox;
import java.awt.Dimension;
import java.net.URL;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.html.HTMLDocument;
public class HtmlText implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new HtmlText());
}
private JEditorPane viewer;
private HTMLDocument document;
@Override
public void run() {
try {
URL text = HtmlText.class.getResource("screen.html");
viewer = new JEditorPane(text);
viewer.setEditable(false);
viewer.setPreferredSize(new Dimension(400, 300));
JFrame frame = new JFrame("screen");
frame.setContentPane(new JScrollPane(viewer));
frame.pack();
frame.setLocationByPlatform(true);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
document = (HTMLDocument) viewer.getDocument();
viewer.addHyperlinkListener(onLink);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
private HyperlinkListener onLink = new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) {
return;
}
String message = "pressed <" + e.getDescription() + ">";
try {
Element out = document.getElement("output");
document.insertString(out.getEndOffset() - 1,
message + "\n", null);
} catch (BadLocationException ex) {
ex.printStackTrace();
}
}
};
}
</pre><br />
<tt>screen.html</tt>:<br />
<pre class="prettyprint lang-html"><html><body>
This is a test.
<p><a href="#link1">link 1</a>
<a href="#link2">link 2</a></p>
<p>output:
<div id="output"></div>
</p>
</body></html>
</pre><br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg46cPnF8FG9ZDgQVgTDAsYrimRm0VGdXfhLBfp51y0SIUOSJpL0PR0y2VWe1e3MyNut4GIZzK6C8wqjmqJKbNge2Hb8WeS9pE5lRLslnz7JtL3DFjQkLS7noUTN5zyU-Y1VL07yY0SVErJ/s1600/Screen+shot+2013-11-21+at+1.47.21+PM.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg46cPnF8FG9ZDgQVgTDAsYrimRm0VGdXfhLBfp51y0SIUOSJpL0PR0y2VWe1e3MyNut4GIZzK6C8wqjmqJKbNge2Hb8WeS9pE5lRLslnz7JtL3DFjQkLS7noUTN5zyU-Y1VL07yY0SVErJ/s320/Screen+shot+2013-11-21+at+1.47.21+PM.png" /></a>Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-10753877354635495362013-11-08T07:18:00.001-04:002013-11-08T07:18:24.377-04:00YouTube to iTunesSometimes I come across a video on <a href="http://www.youtube.com/">YouTube</a> that I would like to turn into a song in iTunes.<br />
<br />
When thinking about puzzles, especially programming puzzles, often my thoughts are around what I have, what I want, and how I can bring the haves and wants closer to each other. As I put effort into the ways to bring the haves and wants together I end up with more haves and more options for wants. I am done when I find that I already have what I want.<br />
<br />
I have a video on YouTube. I want a song in iTunes. iTunes can import mp3 files as songs, so I can also want a mp3 file. I also have <a href="https://www.google.com/intl/en/chrome/browser/">Chrome</a>, and a freshly installed copy of Mac OSX Snow Leopard.<br />
<br />
One option is <a href="http://www.youtube-mp3.org/">YouTube to MP3 Converter</a>, but it can only handle videos up to 20 minutes, and I want to convert a longer one.<br />
<br />
I found a Chrome plugin that allows downloads of the YouTube media file that gets streamed to the browser, <a href="https://spoi.com/software/yto/">YouTube Options</a>. After installing that I can also have a .mp4 or .flv video file.<br />
<br />
I have a <a href="http://blog.sarah-happy.ca/2011/03/video-to-mp3.html">script</a> that I wrote a while ago that turns video files into mp3 files, but to run the script wants ffmpeg installed. <a href="http://www.macports.org/">MacPorts</a> has package for <a href="https://trac.macports.org/browser/trunk/dports/multimedia/ffmpeg/Portfile">ffmpeg</a>, but to <a href="http://www.macports.org/install.php">install MacPorts</a> it wants Xcode installed. Xcode is on the Snow Leopard Installation DVD, so I install it. Now I can <a href="http://www.macports.org/install.php">install MacPorts</a>, so I do. Now I can install ffmpeg, so I do (<span style="font-family: Courier New, Courier, monospace;">sudo port install ffmpeg</span>) .<br />
<br />
Now the script works, so I have what I wanted when I started, almost like magic.<br />
<br />
A video on YouTube --> download the video from YouTube --> convert the downloaded video to mp3 --> import the generated mp3 into iTunes --> a song in iTunes.<br />
<br />Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com1tag:blogger.com,1999:blog-4651329023918655072.post-6411267711610751782013-11-07T10:22:00.003-04:002013-11-07T10:23:03.746-04:00fleece mittensThe mittens I used last winter shrank in the wash, so they did not fit well anymore, so I pondered how to make some out of fleece for a while, made a pattern by tracing another pair of mittens, and made myself some mittens.<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7k1_OYm2bvbzTO4s4pZ2NjKpZ2YcecNhjYdNUlj1blF8WabqzLQUWlcssJdCx2xrIvjQsaj_ssYXv9AmU51m46ZCSbEtLwELvF8zoWGFHC8_qE9GY7WSkAdqMWgb_QZKlBYX0Nuiyu7kH/s1600/131107_093323.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7k1_OYm2bvbzTO4s4pZ2NjKpZ2YcecNhjYdNUlj1blF8WabqzLQUWlcssJdCx2xrIvjQsaj_ssYXv9AmU51m46ZCSbEtLwELvF8zoWGFHC8_qE9GY7WSkAdqMWgb_QZKlBYX0Nuiyu7kH/s320/131107_093323.jpg" width="320" /></a></div>
<div>
<br /></div>
<div>
They are symmetric so I don't need to figure out which one goes on which hand.</div>
<div>
<br /></div>
Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-4111972532741426482013-11-04T14:14:00.002-04:002013-12-05T22:39:21.985-04:00rainbow shoulder bagOne day a month or so ago I decided to make a new rainbow shoulder bag, one with a wider strap that would be more comfortable when I am wearing it for long periods of time. Today I got most of the work done.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLx6vf6m5F9eOYCSsARjfaDBfGELR8cBRM-sFoR60zOzXU56uR7W6nHH5x0bF4CBvgp3aAwJsyapHGJPxZ9YnSey8IDa3G5lex-h5WPUeg-L6och2bq6iQaVw8q64e3XrP2PMvFGzCMHaE/s1600/131104_135515.jpg" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLx6vf6m5F9eOYCSsARjfaDBfGELR8cBRM-sFoR60zOzXU56uR7W6nHH5x0bF4CBvgp3aAwJsyapHGJPxZ9YnSey8IDa3G5lex-h5WPUeg-L6och2bq6iQaVw8q64e3XrP2PMvFGzCMHaE/s320/131104_135515.jpg" /></a><br />
<br />
The strap is filled with crocheted plastic bags giving it a sturdy padded structure. There are some loops of webbing to hang carabiners for holding things like my water bottle or hanging the bag off my backpack. This bag is a bit larger than my previous one, I sized it using a mesh bag from the Dollarama.<br />
<br />
In the future I will add a waterproof liner to keep the contents dry in the rain, and a zipper for easy closing.<br />
<br />
Update: I didn't know at the time, but this bag also works as a backpack, with plenty of space. I just clip a string across the bag and around the strap to make two spaces to put my arms, and because the strap is so wide it is very comfortable when it is weighted down.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5EKhB2LqmG57I86nAHMxBKa3F5D2dKiTLqPxm6Jr7lylhq71OBPs6n7Oe_G02nlLXmiJHj97c1788zuHChOrNU8rwbEjljRuan1KsAxMK69CW3DNeumX6lX-1d_tjB3QcjSfzpNf6lz3q/s1600/20131201_132103.jpg" imageanchor="1"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5EKhB2LqmG57I86nAHMxBKa3F5D2dKiTLqPxm6Jr7lylhq71OBPs6n7Oe_G02nlLXmiJHj97c1788zuHChOrNU8rwbEjljRuan1KsAxMK69CW3DNeumX6lX-1d_tjB3QcjSfzpNf6lz3q/s320/20131201_132103.jpg" width="320" /></a><br />
<br />
<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-29632804002322384132013-10-20T18:36:00.000-03:002013-10-20T18:38:40.507-03:00Facebook API Hello WorldAfter working through the Facebook API <a href="https://developers.facebook.com/docs/opengraph/getting-started/">Getting Started</a> guide I decided to write an even simpler Hello World application that also displays some debugging information. This is my first Facebook Application.<br />
<br />
<a href="https://googledrive.com/host/0BwPL-62FoB5Id1dWTTJSS2ZaVzg/facebook-hello.html"><tt>facebook-hello.html</tt></a>:<br />
<pre class="prettyprint"><html>
<head><title>Facebook API Hello World</title></head>
<body>
<script type="text/javascript">
window.fbAsyncInit = function() {
// https://developers.facebook.com/docs/reference/javascript/FB.init/
FB.init({
appId: '222165227950143' /* registered application id */,
status: true /* check the login status */,
cookie: true /* set the session cookie */,
xfbml: true /* enable social plugins */
});
// get the initial login status
// https://developers.facebook.com/docs/reference/javascript/FB.getLoginStatus/
FB.getLoginStatus(onLogin);
};
// Load the SDK Asynchronously
(function(d, s, id){
if (d.getElementById(id)) {return;}
var js, fjs = d.getElementsByTagName(s)[0];
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
function onLogin(response) {
document.getElementById('onlogin-count').innerHTML++;
document.getElementById('login-status').innerHTML = response.status;
if(response.status === 'connected') {
var uid = response.authResponse.userID;
document.getElementById('user-id').innerHTML = uid;
readName();
}
}
function readName() {
// https://developers.facebook.com/docs/reference/api/user/
FB.api(
'https://graph.facebook.com/me',
'get',
function(response) {
document.getElementById("user-count").innerHTML++;
var e = document.getElementById('user-name');
if(!response) {
e.innerHTML = '<i>no object</i>';
} else if(response.error) {
e.innerHTML = '<i>Error: ' + response.error.message + '</i>';
} else {
e.innerHTML = response.name;
}
}
);
}
</script>
<p>When we are connected to facebook, read the name of the current user and
display it here. If you would like to read the name of the current user again
or before being connected to facebook, press the "read name" button.</p>
<p>
<!-- https://developers.facebook.com/docs/reference/plugins/login -->
<div><div
class="fb-login-button"
data-scope=""
data-onlogin="onLogin">
</div></div>
(Status: <span id="login-status"><i>???</i></span>)
(User ID: <span id="user-id"><i>???</i></span>)
(onLogin calls: <span id="onlogin-count">0</span>)
</p>
<p>
<input type="button" value="read name" onclick="readName()">
<span id="user-name"><i>???</i></span>
(readName calls: <span id="user-count">0</span>)
</p>
</body>
</html>
</pre><br />
This is my first time working with the Facebook API. I like how all the API calls execute in the background, and not block the browser.<br />
Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com3tag:blogger.com,1999:blog-4651329023918655072.post-83198712696986655982013-10-18T11:07:00.000-03:002013-10-18T21:05:00.386-03:00Escape HTML TextOften when I am putting code examples up I have been manually escaping all the html tag characters to get it into the post. Now I have a piece of JavaScript to do it for me. I will present this as a <a href="http://sscce.org/">short, self contained, correct, example</a>.<br />
<br />
<tt><a href="https://googledrive.com/host/0BwPL-62FoB5Id1dWTTJSS2ZaVzg/escape.html">escape.html</a>:</tt><br />
<pre class="prettyprint"><!DOCTYPE html>
<html><head><title>Escape HTML Text</title></head><body><form>
<div>text = <br>
<textarea id="text" rows="10" cols="80"></textarea></div>
<div><button type="button" onclick="setText(escapeHtml(getText()))">text = escapeHtml(text)</button></div>
<div><button type="button" onclick="setText(unescapeHtml(getText()))">text = unescapeHtml(text)</button></div>
<script type="text/javascript">
//<![CDATA[
function getText() { return document.getElementById("text").value; }
function setText(text) { document.getElementById("text").value = text; }
<b>function escapeHtml(text) {
return text.replace(/&/g,"&amp;").replace(/"/g,"&quot;")
.replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
function unescapeHtml(text) {
return text.replace(/&lt;/g,"<").replace(/&gt;/g,">")
.replace(/&quot;/g,"\"").replace(/&amp;/g,"&");
}</b>
//]]>
</script>
</form></body></html></pre>Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-4746268871193220462013-10-18T02:10:00.001-03:002013-10-18T02:10:43.324-03:00Google Drive Hosting Hello WorldGo to <a href="http://drive.google.com/">Google Drive</a><br />
<br />
Create a folder, I called mine "<a href="https://googledrive.com/host/0BwPL-62FoB5Id1dWTTJSS2ZaVzg/">public html</a>", set the sharing to "Public on the web", in the details for the folder there should now be a link for hosting. Upload whatever static files you would like into the shared folder.<br />
<br />Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com1tag:blogger.com,1999:blog-4651329023918655072.post-83398892373055848672013-10-11T13:46:00.001-03:002013-10-11T13:46:24.616-03:00Multi-media message save format and parsingI have yet to have a phone that can save multi-media messages, so I took it upon myself to manually save them.<br />
<br />
I use a very simple text based key-value format to save them. The keys are free text, and the values may contain arbitrary text, including multiple lines.<br />
<br />
<pre class="prettyprint">small
value
multi-line
line 1
line 2
line 3
</pre><br />
Using this format I just choose some field names for the parts of the message, and separated messages with a field name of "done". Field names may be freely repeated.<br />
<br />
<pre class="prettyprint">type
incoming
subject
A Picture/Video Message!
date
2012-11-27 23:38
from
+15555555555
Sender Name
to
+15656565656
My Name
text
the text of the message goes here...
image
imagejpeg_3.jpg
5e35003a0e65df1065982c07587cf147ccb76b5d.jpg
done
</pre><br />
the first line of a from or to is the number, the second line is the name of the contact. the first line of an image is the file name in the message, the second is the file name of the image in the directory. The other fields names are type, subject, date, text, and slide.<br />
<br />
I broke the parsing of the records into usable data structures in Google Apps Script in two phases, the first one extracted the blocks of fields, and the other turned the fields into a data structure.<br />
<br />
<pre class="prettyprint">function forEachRecord(blob, body) {
var fields = new Array();
var field = { name: "", value: new Array() };
var lines = blob.getDataAsString().split(/\r?\n/, -1);
for(var lineIndex = 0; lineIndex < lines.length; lineIndex++) {
var line = lines[lineIndex];
// indented lines are the field value
if(line.substr(0, 1) == " ") {
field.value.push(line.substr(1));
continue;
}
// blank lines are completely ignored
if(line == "") {
continue;
}
// starting a new field
field = { name: line, value: new Array() };
// records end when a field name of "done" is found
if(line == "done") {
body(fields);
fields = new Array();
continue;
}
// this field is part of the record
fields.push(field);
}
if(fields.length) {
body(fields);
}
}
function forEachMessage(blob, body) {
forEachRecord(blob, function(fields) {
var m = {
type: "",
subject: "",
date: "",
from: new Array(),
to: new Array(),
body: new Array(),
};
for(var fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
var f = fields[fieldIndex];
if(f.name == "type") {
m.type = f.value[0];
continue;
}
if(f.name == "to") {
m.to.push({ address: f.value[0], name: f.value[1] });
continue;
}
if(f.name == "from") {
m.from.push({ address: f.value[0], name: f.value[1] });
continue;
}
if(f.name == "date") {
m.date = f.value[0];
continue;
}
if(f.name == "image") {
m.body.push({ type: "image", name: f.value[0], file: f.value[1] });
continue;
}
if(f.name == "text") {
m.body.push({ type: "text", value: f.value.join("\n") });
continue;
}
if(f.name == "slide") {
m.body.push({ type: "slide" });
continue;
}
// if we get here we found an unknown field
}
body(m);
});
}
</pre><br />
This looks similar to a loop in the function that uses it.<br />
<br />
<pre class="prettyprint">forEachMessage(file.getBlob(), function(message) {
messages.push(message);
});
</pre><br />
Turning the parsed messages into an easily human readable output is not difficult from here, and that was my overall objective.<br />
Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-82468189288873114962013-10-09T14:33:00.001-03:002013-10-11T11:38:38.554-03:00LG Neon message parsingSometimes I enjoy looking back over very old text messages from my phone. My current phone is an old LG Neon. It can save text messages to a text file on the SD card so I don't loose them when the text message memory on the phone fills up, but can not save the multimedia messages. I desire to keep all of these and view them later. I also decided that I would like to keep the data on Google Drive, and to get some experience with Google Apps Script. This all leads to taking the save files from my phone and making them useful with a Google Apps Script.<br />
<br />
The LG Neon saves text messages as UTF-16LE text, however the leading character of the text indicates that they are saved as UTF-16BE. The only character set that is documented to be supported in apps script is UTF-8, so I did the UTF-16 decoding manually in the script.<br />
<br />
<pre class="prettyprint">function decodeLgText(bytes) {
// The LG Neon saves text messages in UTF-16LE, with the header bytes for UTF-16BE
var str = "";
for (var i = 2; i < bytes.length; i += 2) {
var charcode = bytes[i] & 0xff | ((bytes[i+1] & 0xff) << 8);
if (charcode < 0xd800 || charcode >= 0xe000) {
str += String.fromCharCode(charcode);
} else {
i += 2;
var charcode1 = bytes[i] & 0xff | ((bytes[i + 1] & 0xff) << 8);
charcode = 0x10000 + ( ((charcode & 0x3ff) << 10) | (charcode1 & 0x3ff) );
str += String.fromCharCode(charcode);
}
}
return str;
}
</pre><br />
Two blocks of text from the export file looks like this:<br />
<pre class="prettyprint">1) From : +1555555555(Sample Name)
Sent : 2013/04/11 17:58
Contents :
See you
227) To : +15555555555(Dear Friend)
Sent : 2013/04/03 20:37
Contents :
Back in my very warm fun fur hammock tonight, ther
e was some pretty snow today, and i get to sleep i
n a bit tomorrow :)
Sweet dreams! <3
</pre>
The contents section of the records is interesting, the raw text message is broken up with "\r\n" line breaks, and if there was a line break in the text message it only has a "\n". The record ends with a double "\r\n" line break.
The solution to parsing this that I finally settled on is two parts, the first decodes each record into an array of lines, with the indentation and message number stripped off, and the second turns the array of lines into a useful data structure.
<pre class="prettyprint">function forEachLgBlock(blob, body) {
var blocks = decodeLgText(blob.getBytes());
blocks = blocks.split("\r\n\r\n");
for(var blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
var block = blocks[blockIndex];
if(block == "") {
continue;
}
var lines = block.split("\r\n", -1);
lines[0] = lines[0].replace(/^\d+\)/, " ");
for(var lineIndex = 0; lineIndex < lines.length; lineIndex++) {
lines[lineIndex] = lines[lineIndex].replace(/^ /, "");
}
body(lines);
}
}
function forEachLgSms(blob, body) {
forEachLgBlock(blob, function(lines) {
var m = {
type: "",
subject: "",
date: "",
from: new Array(),
to: new Array(),
body: [ { type: "text", value: "" } ]
};
var lineIndex;
for(lineIndex = 0; lineIndex < lines.length; lineIndex++) {
var line = lines[lineIndex];
var match;
match = line.match(/^To : (.*?)\((.*?)\)$/);
if(match) {
m.to.push([ { address: match[1], name: match[2] } ]);
m.type = "outgoing";
continue;
}
match = line.match(/^To : (.*)$/);
if(match) {
m.to.push([ { address: match[1] } ]);
m.type = "outgoing";
continue;
}
match = line.match(/^From : (.*?)\((.*?)\)$/);
if(match) {
m.from.push([ { address: match[1], name: match[2] } ]);
m.type = "incoming";
continue;
}
match = line.match(/^From : (.*)$/);
if(match) {
m.from.push([ { address: match[1] } ]);
m.type = "incoming";
continue;
}
match = line.match(/^Sent : (\d\d\d\d)\/(\d\d)\/(\d\d) (\d\d:\d\d)$/);
if(match) {
m.date = match[1] + "-" + match[2] + "-" + match[3] + " " + match[4];
continue;
}
match = line.match(/^Contents :$/);
if(match) {
break;
}
}
for(lineIndex++; lineIndex < lines.length; lineIndex++) {
m.body[0].value = m.body[0].value + lines[lineIndex];
}
m.body[0].value.replace(/\n\r/g, "\n");
body(m);
});
}
</pre>In the main program this parser is called with a callback, making it appear similar to a loop.
<pre class="prettyprint">var messages = new Array();
// sms messages
var folder = DriveApp.getFolderById(...);
var files = folder.getFiles();
while(files.hasNext()) {
var file = files.next();
forEachLgSms(file.getBlob(), function(message) {
messages.push(message);
});
}
</pre>The two examples above would parse into this:
<pre class="prettyprint">[
{
type: "incoming",
subject: "",
date: "2013-04-11 17:58",
from: [ { address: "+1555555555", name: "Sample Name" } ],
to: [ ],
body: [ { type: "text", value: "See you" } ]
},
{
type: "outgoing",
subject: "",
date: "2013-04-03 20:37",
from: [ ],
to: [ { address: "+1555555555", name: "Dear Friend" } ],
body: [ { type: "text", value: "Back in my very warm fun fur hammock tonight, there was some pretty snow today, and i get to sleep in a bit tomorrow :)\nSweet dreams! <3" } ]
}
]
</pre><br />
What I did with the multi-media messages will be covered another time.<br />
Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-5439019414932008542013-07-29T14:02:00.000-03:002013-07-29T15:01:21.673-03:00rainbow water bottleJust before I left to hitchhike across the country to deliver one of my rainbow lighters to a close friend in a campground in BC, I painted myself a rainbow swirl metal water bottle.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmzxR5e5wL8b4We__oIrlW10lS_9zBPzUnmDjRzibd06clenhMZVvUE4AfUrN81-osXMnyQqeozK8UX84YIhRgrrSINUwT79mbdOib4ak-esV82R1qe6tT_4P0IF14JUoRYSK23xNJTeSj/s1600/130521_174027.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmzxR5e5wL8b4We__oIrlW10lS_9zBPzUnmDjRzibd06clenhMZVvUE4AfUrN81-osXMnyQqeozK8UX84YIhRgrrSINUwT79mbdOib4ak-esV82R1qe6tT_4P0IF14JUoRYSK23xNJTeSj/s320/130521_174027.jpg" width="320" /></a>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUWNOh-C1fIunuEwPi84xw9u4ggAcSgaednxs1l4Db7peY6tn-4UNVmuO1ACnPSZUPWSTAK3eb1EuEAHPfvJo_Pbm4fIPVn4g0YLdtdqVYCrZhF-8JvtixLvgxrW_F_yPD01h5uWfaHFx8/s1600/130521_174040.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUWNOh-C1fIunuEwPi84xw9u4ggAcSgaednxs1l4Db7peY6tn-4UNVmuO1ACnPSZUPWSTAK3eb1EuEAHPfvJo_Pbm4fIPVn4g0YLdtdqVYCrZhF-8JvtixLvgxrW_F_yPD01h5uWfaHFx8/s320/130521_174040.jpg" width="320" /></a></div>
<br />
The bottle has served me very well and even has a tendency of finding me when I leave it by itself for a while outside of where I am residing at the moment.<br />
Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com3tag:blogger.com,1999:blog-4651329023918655072.post-68032198558967576982013-05-03T18:03:00.000-03:002013-05-03T18:03:36.369-03:00Wool lined hammockI just finished my wool lined hammock for traveling Sunday...<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6qBp1q-0XR7FG0Rh8LtFUQry0PyXCYbSiVl5lwHYtTBl8QpWGuTPCCw3ks4BkPfGGFgeEOgjqqeeC0-e0Q8Lnda3cekg_Be2nul27uyCAht0YGB3Oft0d4wqFa0NFOyFObVhjwJCCFlmy/s1600/130503_172429.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6qBp1q-0XR7FG0Rh8LtFUQry0PyXCYbSiVl5lwHYtTBl8QpWGuTPCCw3ks4BkPfGGFgeEOgjqqeeC0-e0Q8Lnda3cekg_Be2nul27uyCAht0YGB3Oft0d4wqFa0NFOyFObVhjwJCCFlmy/s320/130503_172429.jpg" width="240" /></a></div>
<br />
It feels like like a tent on the inside when zipped up, though the similarity ends at there being a dark floor and light walls and ceiling. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPPKI1MSjudaRc1HpVi5OHzKZKyKOK4cwlXumNq18ZPXP9I63u6rVziioVrrbh6skOv33AAa1Kjc5Db7QNTOtfGnMSelelKZ_rCn3GatJT4zVyRmhclh-VCysfaCQROUM_G4HAFA8_2E96/s1600/130503_174110.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPPKI1MSjudaRc1HpVi5OHzKZKyKOK4cwlXumNq18ZPXP9I63u6rVziioVrrbh6skOv33AAa1Kjc5Db7QNTOtfGnMSelelKZ_rCn3GatJT4zVyRmhclh-VCysfaCQROUM_G4HAFA8_2E96/s320/130503_174110.jpg" width="320" /></a></div>
<br />
The outside is nice and plain, all windbreaker nylon. That is my tarp tucked to the side. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi30srIkkFlxoYxq2Rw50WEwrYDvt7Xw5fHuHqGSjoEMWuIhnd-QUZ7XkSFwp7gH3lqmSQyfi-6fXU1tDS25Uox9TmIhLUAYS0XTW9ZtZiIt1Kzt7Cyn3m6kXNsTwt-2nQHpaXP3EpMdStn/s1600/130503_174143.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi30srIkkFlxoYxq2Rw50WEwrYDvt7Xw5fHuHqGSjoEMWuIhnd-QUZ7XkSFwp7gH3lqmSQyfi-6fXU1tDS25Uox9TmIhLUAYS0XTW9ZtZiIt1Kzt7Cyn3m6kXNsTwt-2nQHpaXP3EpMdStn/s320/130503_174143.jpg" width="320" /></a></div>
<span id="goog_143129409"></span><span id="goog_143129410"></span><br />
<span id="goog_143129409">The brown part is the wool, the rest is light nylon.</span><br />
<br />
The hammock does not take up much room in my bag, and I have taken it down to zero with my heavy clothes on for a night, before the zippers were added, I expect I can sleep in this just past zero now.Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0tag:blogger.com,1999:blog-4651329023918655072.post-62800122341634708852013-04-18T19:04:00.002-03:002013-04-18T19:05:08.237-03:00Dehydrated orange cookiesI dehydrated some oranges, but instead of just slicing them thinly, I peeled them and sliced them in four, making round slices. They took a long time to dry, but they look like and feel like cookies, but taste like orange. Two cookies makes half an orange. I also dried the peeling since it is edible.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6fk5de7QDWtGMx7S3BjEzU0QFUlUGuYCUaJQJi0YKnC1waJWBYBok8w5hgt64R3vSU8-8BraTLwE35NKlse5ViEfGVIWJJlCxjefdf4S9ZjP-evio0O3VajzEcgFfoiybs-DW3z7CCZ6P/s1600/130411_113126.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6fk5de7QDWtGMx7S3BjEzU0QFUlUGuYCUaJQJi0YKnC1waJWBYBok8w5hgt64R3vSU8-8BraTLwE35NKlse5ViEfGVIWJJlCxjefdf4S9ZjP-evio0O3VajzEcgFfoiybs-DW3z7CCZ6P/s320/130411_113126.jpg" width="320" /></a>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjx1fNWn-9VqP0x9mGys4d2vQOaq0csa7CVY_fEKm0PwWcYyqBf6hOkhyphenhyphen2SSLX5R_DDT54_0EWpWaDsWHJaU2yVC7l_a1_JhzL1lfMdC1z_loFn_9JNgIz-e80wMr1kK2NFo2b1YNRbrhpn/s1600/130414_130102.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjx1fNWn-9VqP0x9mGys4d2vQOaq0csa7CVY_fEKm0PwWcYyqBf6hOkhyphenhyphen2SSLX5R_DDT54_0EWpWaDsWHJaU2yVC7l_a1_JhzL1lfMdC1z_loFn_9JNgIz-e80wMr1kK2NFo2b1YNRbrhpn/s320/130414_130102.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgP7kmaKDyD8OtWuveQPZKuPt68J8UOU5VIvDtGOYA4hGCXVAJkbTtlJiDK6zIo1QedUsulDcD9kRP_UjT86dAG1oTa1_Pq3A-k9mV-nqEbD7qmvL064AWF9pk352q_B7P0drtnC65UhyphenhyphenQf/s1600/130415_101023.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgP7kmaKDyD8OtWuveQPZKuPt68J8UOU5VIvDtGOYA4hGCXVAJkbTtlJiDK6zIo1QedUsulDcD9kRP_UjT86dAG1oTa1_Pq3A-k9mV-nqEbD7qmvL064AWF9pk352q_B7P0drtnC65UhyphenhyphenQf/s320/130415_101023.jpg" width="320" /></a></div>
<br />
So yummy, the oranges don't last long like this.<br />
<br />Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com1tag:blogger.com,1999:blog-4651329023918655072.post-80781948924642854422013-03-07T14:39:00.001-04:002013-03-07T14:42:28.058-04:00funky food jarI had another empty jar taped and ready to paint... This time I decided to put a drop of paint on it, spread it using a brush without overlapping paint that was already on it, change colors, and repeat until it was covered.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglYzE8n2pgbwoevv2iG_hc_W0HBCmgknLxyjSjivx1XgeZJZKM_5PSfJbXE-MgAY7eFzRv19CDpLYJ2PMlB25xVwApnLoG3nzEQzQTatz0G9DJLHGada8NLGIZ-0KgIq1l7jewi7UJW8g9/s1600/130307_083437.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglYzE8n2pgbwoevv2iG_hc_W0HBCmgknLxyjSjivx1XgeZJZKM_5PSfJbXE-MgAY7eFzRv19CDpLYJ2PMlB25xVwApnLoG3nzEQzQTatz0G9DJLHGada8NLGIZ-0KgIq1l7jewi7UJW8g9/s320/130307_083437.jpg" width="240" /></a>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1mFC_nbKmNOypuyNPTd8Esn0OUbUTfN5y6UXSMbsz71o7KA2aRJ_4SCtvTFP1CaN1HNjXjSb1ct3HM46afVTfs2a3r3HvklhZJCQgTnfFzZSQOdPWoo9ToAxEgUnP41aKEe6VpgYPorBD/s1600/130307_083501.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1mFC_nbKmNOypuyNPTd8Esn0OUbUTfN5y6UXSMbsz71o7KA2aRJ_4SCtvTFP1CaN1HNjXjSb1ct3HM46afVTfs2a3r3HvklhZJCQgTnfFzZSQOdPWoo9ToAxEgUnP41aKEe6VpgYPorBD/s320/130307_083501.jpg" width="240" /></a>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwswUfEE4VOk3Kw5GMJaBk8r_BNuOKHsMmMFrYKGMhDyxpSAiehsKk_9tYRHEa-NsQ8cq5XvhlnzGs_CCMsJgfjc_-JZmxrL81FDHgI_MX5LcV55scQ6rmxzbyrD0rnKTXhajTOVb4iFPF/s1600/130307_083515.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwswUfEE4VOk3Kw5GMJaBk8r_BNuOKHsMmMFrYKGMhDyxpSAiehsKk_9tYRHEa-NsQ8cq5XvhlnzGs_CCMsJgfjc_-JZmxrL81FDHgI_MX5LcV55scQ6rmxzbyrD0rnKTXhajTOVb4iFPF/s320/130307_083515.jpg" width="240" /></a>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtF2PQwRt90MplL8GewbCu0RD1Uc_cnfgj2EFo5O5y5QUo1S_Icm8dFxWSAt0nYnrXuMFdB648nYP7o3rX0n7PCKKc_0OBOp7-OxzjjJZU5lJUzGbMT5tzUQMYH0egSWLNMrvOYEKdHC4N/s1600/130307_083530.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtF2PQwRt90MplL8GewbCu0RD1Uc_cnfgj2EFo5O5y5QUo1S_Icm8dFxWSAt0nYnrXuMFdB648nYP7o3rX0n7PCKKc_0OBOp7-OxzjjJZU5lJUzGbMT5tzUQMYH0egSWLNMrvOYEKdHC4N/s320/130307_083530.jpg" width="240" /></a></div>
<br />Anonymoushttp://www.blogger.com/profile/15760286573616674510noreply@blogger.com0