tag:blogger.com,1999:blog-69315029566613920722024-03-14T06:22:38.604+01:00gsusmonzongsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.comBlogger47125tag:blogger.com,1999:blog-6931502956661392072.post-13946440455408068392021-10-06T20:01:00.006+02:002021-10-06T20:01:55.054+02:00Install mysql with docker in your local machine (ubuntu 20.04)<p>I had to use another laptop for some days and I didnt want to install packages that left garbage behind when moving away from this machine.</p><p>These are the two guides I followed for reference : </p><ul style="text-align: left;"><li><a href="https://phoenixnap.com/kb/mysql-docker-container">https://phoenixnap.com/kb/mysql-docker-container</a></li><li><a href="https://towardsdatascience.com/connect-to-mysql-running-in-docker-container-from-a-local-machine-6d996c574e55">https://towardsdatascience.com/connect-to-mysql-running-in-docker-container-from-a-local-machine-6d996c574e55</a> </li></ul><h4 style="text-align: left;"> Preparation</h4><div style="text-align: left;"><p style="text-align: left;">In my case docker was already installed. <span style="font-family: courier;">docker</span> can run from my user, so no need to append `<span style="font-family: courier;">sudo</span>` to any `<span style="font-family: courier;">docker</span>` command </p><h4 style="text-align: left;">Install mysql server via docker</h4><p style="text-align: left;">The key here is to use the <a href="https://hub.docker.com/_/mysql">official mysql package from docker hub</a></p><p style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;"># https://hub.docker.com/_/mysql<br />docker image ls<br />docker pull mysql:latest<br />docker image ls</span></span></p><p style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;"># create a volume in local machine<br />docker volume create mysql-data<br />docker volume ls<br />docker volume inspect mysql-data<br /></span></span></p><p style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;"># map a local directory to host the config<br />mkdir -p /home/jesus/projects/docker/mysql-volume/conf.d<br />echo -e '[mysqld]\nmax_connections=100\nmysqlx= 0' > /home/jesus/projects/docker/mysql-volume/conf.d/my-custom.cnf</span></span></p><p style="text-align: left;">then create the container</p><p style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">docker run \<br />--detach \<br />--name=mysql_server \<br />--publish 3306:3306 \<br />--volume=/home/jesus/projects/docker/mysql-volume/conf.d:/etc/mysql/conf.d \<br />--volume=mysql-data:/var/lib/mysql \<br />--env MYSQL_ROOT_PASSWORD=root \<br />--env MYSQL_ALLOW_EMPTY_PASSWORD=yes \<br />mysql:latest<br /><br />docker ps<br />docker logs mysql_server</span></span></p><p style="text-align: left;">change root password<br /><span style="font-size: x-small;"><span style="font-family: courier;"><br /># access to the container in interactive mode<br />docker exec -it mysql_server bash</span></span></p><p style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;"><br />#supply initial root password (to enter mysql inside the container)<br />mysql -uroot -proot</span><br /></span></p><p style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;"># SET PASSWORD FOR 'root' = 'something-super-secret';<br />SET PASSWORD FOR 'root' = '';<br />FLUSH PRIVILEGES;<br /><br /># enable docker access from anywhere (probably the image has this already)<br />UPDATE mysql.user SET host = '%' WHERE user='root';<br />FLUSH PRIVILEGES;</span></span></p><h4 style="text-align: left;">Install mysql client</h4><p style="text-align: left;">Install the client in your machine that accesses the mysql server running in the docker container. <b>For other distributions</b> adjust the method to install the mysql <b>client</b> (only the client is needed). The mysql site provides that information.<br /></p><p style="text-align: left;">First, add the mysql repo. Go to <a href="https://dev.mysql.com/downloads/repo/apt/">https://dev.mysql.com/downloads/repo/apt/</a> and get the latest repository available. In my case (@2021-10-01). Adjust the .deb package accordingly. </p><span style="font-size: x-small;"><span style="font-family: courier;"># add the repo<br />wget https://dev.mysql.com/get/mysql-apt-config_0.8.19-1_all.deb<br />#Click OK with the default options or select which mysql you want<br />sudo dpkg -i mysql-apt-config_0.8.19-1_all.deb <br />rm mysql-apt-config_0.8.19-1_all.deb<br /><br /># install the client from the repo<br />sudo apt-get update<br />sudo apt-get install mysql-client<br /># sudo apt-get install mysql-server</span></span><p style="text-align: left;">ensure it works</p><p style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">mysql -uroot -h127.0.0.1 -P3306<br /># or with password<br /># mysql -uroot -p -h127.0.0.1 -P3306</span></span> <br /></p><p style="text-align: left;"></p><p style="text-align: left;">😄 Fin!</p></div><div style="text-align: left;"><h4 style="text-align: left;">P.S. Useful commands</h4><span style="font-size: x-small;"><span style="font-family: courier;">docker start mysql_server<br />docker stop mysql_server<br />docker restart mysql_server<br />docker ps<br />docker volume inspect mysql-data<br />docker container ls --all<br /><br />mysql -uroot -p -h127.0.0.1 -P3306<br /><br /># dangerous!<br />docker rm mysql_server<br />docker volume rm mysql-data</span></span><br /><h4 style="text-align: left;"><br /></h4><p style="text-align: left;"><br /></p></div>gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com10tag:blogger.com,1999:blog-6931502956661392072.post-2930407185071625272019-10-23T13:09:00.002+02:002019-10-23T13:09:45.730+02:00Pattern to solve circular dependencies in node,This is an useful pattern to avoid circular dependencies while requiring other files in node.<br />
<br />
Circular dependencies manifest when a require returns an empty object: "<span class="st"><em>the circular dependency means that other code receives an incomplete module object</em>"</span><br />
<br />
<span class="st">The solution is to 'augment' the exports instead of replacing it. </span><br />
<span class="st">So, if another module got a reference to the exported members they are eventually completed.</span><br />
<br />
<span style="font-size: x-small;"><span class="st"><span style="font-family: "Courier New", Courier, monospace;">//requires at the <b>beginning</b>, as usual</span></span></span><br />
<span class="st"><span style="font-family: "Courier New", Courier, monospace; font-size: x-small;">//.. </span></span><br />
<br />
<br />
<span class="st"><span style="font-family: "Courier New", Courier, monospace; font-size: x-small;">const PublicMethods = {</span></span><br />
<span class="st"><span style="font-family: "Courier New", Courier, monospace; font-size: x-small;"> method1: () => {},</span></span><br />
<span class="st"><span style="font-family: "Courier New", Courier, monospace; font-size: x-small;"> method2: (x) => {...},</span></span><br />
<span class="st"><span style="font-family: "Courier New", Courier, monospace; font-size: x-small;"> ...</span></span><br />
<span class="st"><span style="font-family: "Courier New", Courier, monospace; font-size: x-small;">]; </span></span><br />
<br />
<span class="st"><span style="font-family: "Courier New", Courier, monospace; font-size: x-small;">// -- private & other methods</span></span><br />
<span class="st"><span style="font-family: "Courier New", Courier, monospace; font-size: x-small;">... </span></span><br />
<span class="st"><span style="font-family: "Courier New", Courier, monospace; font-size: x-small;">...</span></span><br />
<br />
<span class="st"><span style="font-family: "Courier New", Courier, monospace; font-size: x-small;">//augment the exports at the <b>end of the file</b>:</span></span><br />
<span class="st"><span style="font-family: "Courier New", Courier, monospace; font-size: x-small;">Object.assign(module.exports, PublicMethods); </span></span><br />
<span class="st"><br /> </span><br />
<span class="st"><br /></span>gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com3tag:blogger.com,1999:blog-6931502956661392072.post-58489851963450906162017-08-23T11:54:00.000+02:002017-08-23T11:54:23.811+02:00Get Response Time with Curl - curltimeSave this script in your path (echo $PATH) as <span style="font-family: "Courier New",Courier,monospace;"><b>curltime</b></span>:<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"><br /></span></span>
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">#!/bin/bash<br /><br />/usr/bin/curl -o /dev/null \<br />-s -w " time_namelookup: %{time_namelookup}\n time_connect: %{time_connect}\n time_appconnect: %{time_appconnect}\n time_pretransfer: %{time_pretransfer}\n time_redirect: %{time_redirect}\n time_starttransfer: %{time_starttransfer}\n ----------\n time_total: %{time_total}\n" \<br />$1</span></span><br />
<br />
Make it executable<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">chmod 0755 /usr/local/bin/curltime</span></span><br />
<br />
<br />
Now you can use it to check the response time of an url. Ex:<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><br /></span></span>
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">curltime https://marca.com</span></span><br />
<br />
Source: based on https://viewsby.wordpress.com/2013/01/07/get-response-time-with-curl/gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com2tag:blogger.com,1999:blog-6931502956661392072.post-18294949947694505792017-06-14T17:15:00.001+02:002017-06-14T17:17:09.317+02:00Merge 2 commits when there are commits in betweenSuper handy trick based on based on <a href="https://stackoverflow.com/questions/30704254/merge-two-commits-where-commits-are-between">https://stackoverflow.com/questions/30704254/merge-two-commits-where-commits-are-between</a><br />
<br />
Lets say we have 2 commit we want to merge<br />
<span style="font-family: "courier new" , "courier" , monospace;"><br />f68ffb5 commit 6<br />...<br />d294fac commit 1</span><br />
<br />
This is important: you have to give reference to a <b>commit before the first</b> one involved (in this case, the one that you want to squash to). Here we make the "before" reference with ^.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;"># rebase to the commit **before** commit 1</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">git rebase -i d294fac^</span><br />
<br />
You invoked an interactive rebase. An editor window opens. Rearrange commits as desired (read the notes in the editor that opens)<br />
<br />
In this list the commits go in reverse order (not as in git log): from the earliest to the latest.<br />
<br />
Rearrange lines in the following way:<br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;">pick d294fac //--- commit 1<br />squash f68ffb5 //--- commit 6<br />pick <sha1> //--- commit 2<br />pick <sha1> //--- ommit 3<br />pick <sha1> //--- commit 4<br />pick <sha1> //--- commit 5</sha1></sha1></sha1></sha1></span><span style="font-family: "courier new" , "courier" , monospace;"></span><span style="font-family: "courier new" , "courier" , monospace;"></span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>or (leave the squashed commits in last place)<br />
<span style="font-family: "courier new" , "courier" , monospace;">pick d294fac </span><span style="font-family: "courier new" , "courier" , monospace;"> //--- commit 1<br />squash f68ffb5 </span><span style="font-family: "courier new" , "courier" , monospace;"> //--- commit 6<br />pick <sha1> </sha1></span><span style="font-family: "courier new" , "courier" , monospace;"> //--- commit 2<br />pick <sha1> </sha1></span><span style="font-family: "courier new" , "courier" , monospace;"> //--- ommit 3<br />pick <sha1> </sha1></span><span style="font-family: "courier new" , "courier" , monospace;"> //--- commit 4<br />pick <sha1> </sha1></span><span style="font-family: "courier new" , "courier" , monospace;"> //--- commit 5</span>gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com3tag:blogger.com,1999:blog-6931502956661392072.post-45845068367093464362016-09-06T22:19:00.000+02:002016-09-06T22:21:41.873+02:00Back In Time: Backups for your personal computer<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b>'Back in Time</b>'<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"> ( </span></span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><a href="http://backintime.le-web.org/">http://backintime.le-web.org/</a> <span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">)</span></span> is a linux program to make </span></span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"> regular backups<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">. </span>I<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"> </span>recommend it. </span><br />
<h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">W<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">h<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">y?</span></span></span></span></h3>
<ul>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">simple. Linux only.</span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">it uses <i>incremental</i> backups and <i>hardlinks</i>, saving time and space between 'snapshots'</span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">'snapshots' are saved as folders. Each snapshot is seen as a 'copy' of the system at a given time, but you still can get the 3-weeks ago version of a file, just by moving inside the file-tree of the snapshot. This is key: <b>you can plug your backup drive to another computer and access the snapshots from the file explorer</b>. No need to install anything to read the backup</span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">autoremove old backups based on age or free space</span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">backups are made to a <b>drive or</b> a remove<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"> machine (ssh)</span>. <b>No cloud storage</b> option (S3, box, etc)</span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">handles backups of several machines with no extra configuration. Snapshots of different machines are stored in different folders</span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Just works</span></li>
</ul>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">How I use it</span></h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">(for all my computers) I run Back in time backups on demand (not scheduled) and unencrypted. Both options are available if needed.</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Backups are stored in a ext4 external drive (ext4 is linux native but still read-accessible from OSX and Windows)</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Install</span></h3>
<div dir="ltr" id="docs-internal-guid-57df00f0-efe9-62ec-bf40-b7b6c7d6a8e7" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-size: 14.6667px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">This works for ubuntu. For others, go to the original <a href="http://backintime.le-web.org/">http://backintime.le-web.org/</a></span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-size: 14.6667px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;"><br /></span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 14.6667px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">sudo add-apt-repository ppa:bit-team/stable</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 14.6667px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">sudo apt-get update</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 14.6667px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">sudo apt-get install backintime-gnome</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
</div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b>Note: </b>latest versions are <span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">only qt-<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">based<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">. Y<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">ou c<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">ould in<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">stall the `backintime-qt4` package instead <span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">`backintim<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">e-gnome`</span></span></span></span></span></span></span></span></span></span>
<br />
<h4>
<span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Configure</span></span></h4>
</div>
<h3 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
</h3>
<h3 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Connect the external drive for the backup<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">s</span>, and launch B<i>ack in Time</i>. The first time you need to configure how to make backups for the computer (accessible from settings / preferences as well). </span></span></span></span></h3>
<h3 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;"> </span></span></span></span></h3>
<h3 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">In the settings, you need to set where and when to save snapshots. For example I choose</span></span></span></span></h3>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b>[General] </b>tab</span></span></div>
<ul>
<li><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Profile: Main profile</span></span></span></li>
<li><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Mode: local (or local encrypted)</span></span></span></li>
<li><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Where to save snapshots / destination: your drive , partition or subfolder. </span></span></li>
<li><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Schedule disabled. I will save a new snapshot on demand</span></span></span></li>
</ul>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjacwLB4SSfBoSdwOV_EFJvA26F1r8to5jGlMiLfBKcD68SghC8OnJHA4uilHfahF_p-RCILRw4aayR4dhtmPRg6dm0BgAVLS2k6XKdPEf2ED3AXcIpt1Em3XjOOXsSNecOENp5IWospTG/s1600/Selection_004.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="376" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjacwLB4SSfBoSdwOV_EFJvA26F1r8to5jGlMiLfBKcD68SghC8OnJHA4uilHfahF_p-RCILRw4aayR4dhtmPRg6dm0BgAVLS2k6XKdPEf2ED3AXcIpt1Em3XjOOXsSNecOENp5IWospTG/s640/Selection_004.png" width="640" /></a><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b>[Include] , [Exclude]</b> tab</span></span><br />
<span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">I usually include the home <b> </b>folder, or just the Documents, Pictures and .ssh folder. I exclude the Music and Videos folder.</span></span><br />
<br />
<b><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">[Autoremove]</span></span></b><br />
<span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">defaults are good for me</span></span><br />
<span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></span>
<b><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">[Options] </span></span></b><br />
<ul>
<li><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;"> Check Use checksum to detect changes</span></span></span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"></span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-size: small; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;"><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Check </span></span></span>Full rsync mode (or not)</span></span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"></span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-size: small; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;"><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Check </span></span></span>Backup replaced files on restore</span></span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-size: small; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Uncheck 'continue on errors'</span></span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"></span><b><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></span></b></li>
</ul>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdhzaBsY6Nh11e_w4P2ON5UKSMk6si1dYOwMVzrHH25NkGuc5jM7f8DUXZ63FNfXAEUWmd_jySq6NLPZqSsj-PakyiPC_Nbw0OFiCLUzpVimAi2pKh8spwoMyvSibjIwNMdQP6pmAc296-/s1600/Selection_005.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="484" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdhzaBsY6Nh11e_w4P2ON5UKSMk6si1dYOwMVzrHH25NkGuc5jM7f8DUXZ63FNfXAEUWmd_jySq6NLPZqSsj-PakyiPC_Nbw0OFiCLUzpVimAi2pKh8spwoMyvSibjIwNMdQP6pmAc296-/s640/Selection_005.png" width="640" /></a><br />
<br />
<span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b>[Expert options]</b></span></span><br />
<br />
<ul>
<li><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="font-size: small;"><span style="font-family: "arial";">U</span>ncheck any option related to cron</span></span></span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-size: small; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Check ‘Run ionice’ when taking a manual snapshot</span></span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-size: small; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">you <i>may</i> check 'Preserve ACL' ( as src and dst are Ext4, otherwise <u><i>uncheck</i></u> ). I finally <i>uncheck</i> it since gave me errors for some of my files</span></span><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b> </b></span></span></li>
</ul>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtRT0Swn7WBzZLfYg8RmaA_46xOKRtmzhDj-j44kAThyphenhyphen5xU0CyFMPuPCIi-5klqN88RzSoQR6lskGyAurur7qebjSpKfmnCfinELNCqj_BXCYrx3jnelO_ig2PneNMT59P5dN4kkzjjK4o/s1600/Selection_006.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtRT0Swn7WBzZLfYg8RmaA_46xOKRtmzhDj-j44kAThyphenhyphen5xU0CyFMPuPCIi-5klqN88RzSoQR6lskGyAurur7qebjSpKfmnCfinELNCqj_BXCYrx3jnelO_ig2PneNMT59P5dN4kkzjjK4o/s640/Selection_006.png" width="632" /></a><span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-size: small;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">-</span></span></span><br />
<br />
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="font-size: small;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Save configuration > OK </span></span></span></div>
<h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">New backup</span></h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">You can make a new backup clicking on the 'disk' icon on the main toolbar. The first backup might take some minutes. Newer snapshots execute faster.</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhig8Lz74Go6jQ8osucjRwXc_tJE0HFXRkIhH-p_8HS27D202ViD17mE9JAWqz8Vie3amlEBEXClEpDykDyhoGCsdHAFQVEJHryfQwVxWgvoFqenLy33KYtQ6_EpUaJ7da67AGm_OAkw1P/s1600/Selection_007.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="328" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhig8Lz74Go6jQ8osucjRwXc_tJE0HFXRkIhH-p_8HS27D202ViD17mE9JAWqz8Vie3amlEBEXClEpDykDyhoGCsdHAFQVEJHryfQwVxWgvoFqenLy33KYtQ6_EpUaJ7da67AGm_OAkw1P/s640/Selection_007.png" width="640" /></a><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-size: small; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">In case you need to back up files with special permission, you might need to run your backintime with sudo. The profile id is in the <i>Settings > General </i>, in the Advanced section</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><span style="background-color: transparent; color: black; font-size: small; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;"> </span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-size: x-small;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">sudo -i backintime-qt4 --config ~/.config/backintime/config --profile-id 1</span></span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<h3>
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Uninstall</span></h3>
</div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-size: x-small;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">sudo ppa-purge ppa:bit-team/stable</span></span></span></div>
<span style="font-size: x-small;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">sudo apt-get remove backintime-*</span></span></span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<br /></div>
gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com6tag:blogger.com,1999:blog-6931502956661392072.post-34660895757453440002016-09-03T13:13:00.006+02:002016-09-03T13:17:51.171+02:00Nautilus: Add touch action in context menuThis is a modification I make to nautilus in every computer I own.<br />
<br />
Just make a 'touch' option available when you select a file or folder. This tutorial can be modified to include other commands as well. I have used it for Ubuntu 12.04, 14.04 and 16.04.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuatTjp1OWQc_owJNUeyHaqIrWoPjLPV8oqP1h9qERiti6NzGDwSQVYcvrLut8pBs3yFcv04uofZl9HIvtkbtpn1qyKYndVydGaa8cjHaQ5B-CALPhey-ev0-J03m7sR0uPP3dshObIfV6/s1600/contextual.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuatTjp1OWQc_owJNUeyHaqIrWoPjLPV8oqP1h9qERiti6NzGDwSQVYcvrLut8pBs3yFcv04uofZl9HIvtkbtpn1qyKYndVydGaa8cjHaQ5B-CALPhey-ev0-J03m7sR0uPP3dshObIfV6/s640/contextual.png" width="640" /></a><br />
<br />
First, <b>install nautilus actions</b><br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">sudo apt-get install nautilus-actions</span><br />
<br />
and launch it.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">nautilus-actions</span><br />
<br />
<b>(1) Create a new item</b><br />
<ul>
<li>Create the item using the '+' icon and name the action (Ex: 'Touch'). Use a short name, since it is used in teh context menu</li>
<li>Edit Action and Command tabs accordingliy. Ex:</li>
<ul>
<li><i><span style="font-family: inherit;">Path: /usr/bin/touch</span></i></li>
<li><i><span style="font-family: inherit;">Parameters: %F</span></i> (see Legend button to get more options)</li>
<li><i><span style="font-family: inherit;">Working directory: %d</span></i></li>
</ul>
<li>Save (icon save)</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVTHxloHI1aqkE5kqmy7Q8RRI_EkGDL0aoAoHv-pRuOg4qBbkhpW3X2T2CuMFsE8OvHIyZvYU6NccXUxPonQQHOMoi-UyzVuAYagb0Yob8YVuzSju-ZY0kdGNQKUaOJbqoc7Q34I641lze/s1600/Selection_002.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="323" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVTHxloHI1aqkE5kqmy7Q8RRI_EkGDL0aoAoHv-pRuOg4qBbkhpW3X2T2CuMFsE8OvHIyZvYU6NccXUxPonQQHOMoi-UyzVuAYagb0Yob8YVuzSju-ZY0kdGNQKUaOJbqoc7Q34I641lze/s640/Selection_002.png" width="640" /></a></div>
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy7HaUh27HFhxTSWpNb5HKktkB0qi-pyiMVdBiaSYWI-qikU2J8xLBZinIpg1lNS9PiYCqu-TljPo-9JyMxw2HvAuTnPHQ5WDRWWK1-wRCr-24TSoe_n0SoofQzIFXocYjzy3RGlNqnqIn/s1600/Selection_001.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="324" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy7HaUh27HFhxTSWpNb5HKktkB0qi-pyiMVdBiaSYWI-qikU2J8xLBZinIpg1lNS9PiYCqu-TljPo-9JyMxw2HvAuTnPHQ5WDRWWK1-wRCr-24TSoe_n0SoofQzIFXocYjzy3RGlNqnqIn/s640/Selection_001.png" width="640" /></a><br />
<br />
<br />
<b>(2) Right click</b> on the new action that appeared in the <i>Item list</i> area<br />
<ul>
<li>go to <i>preferences</i></li>
<li>Ensure the '<i>Nautilus menu layout</i>' > '<i>Create a root...</i>' is <b>disabled</b></li>
</ul>
<br />
<b>Restart Nautilus</b><br />
<span style="font-family: "courier new" , "courier" , monospace;">pkill nautilus</span><br />
<br />
<br />
Well done!<br />
<br />gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com2tag:blogger.com,1999:blog-6931502956661392072.post-17886713750160963712015-07-19T09:45:00.002+02:002015-07-19T09:45:23.519+02:00How to determine the time zone of an IP in RubyIn ruby / Rails, you may need to get the timezone of an ip address. <br />
<br />
One of the best options is the <a href="http://dev.maxmind.com/geoip/geoip2/web-services/#MaxMind_APIs" target="_blank">Maxmind.com GeoIP</a> . The solution provided here applies to geolocate an IP address to get the timezone, country, city etc<br />
<br />
<h3>
Local or Remote </h3>
For better results I recommend to use their <b>web service</b> to geolocate a city : http://dev.maxmind.com/geoip/geoip2/web-services .<br />
The service is <b>not free</b> but you can pay 20$ for 50K resolutions. Fair enough.<br />
I write a new post on how to query the web service.<br />
<br />
Another option is to resolve the geolocation <b>locally</b>. Maxmind provides a <b>free database</b> that is updated every month. As a free product, it is less accurate than the web service but gives good results. http://dev.maxmind.com/geoip/legacy/geolite/<br />
<br />
<h3>
Local set up</h3>
We need:<br />
- download the database<br />
- include the `geoip` gem, that will read the database<br />
- our code to query the geoip gem<br />
- link back to Maxmind<br />
<h4>
Download database</h4>
Read http://dev.maxmind.com/geoip/legacy/install/city/ for instructions. It boils down to:<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">cd /tmp</span></span><br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"><span class="crayon-v">wget</span><span class="crayon-h"> </span><span class="crayon-o">-</span><span class="crayon-i">N</span><span class="crayon-h"> </span><span class="crayon-v">http</span><span class="crayon-o">:</span><span class="crayon-c">//geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz</span></span></span><br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"><span class="crayon-c">gunzip </span><span class="crayon-c">GeoLiteCity.dat.gz</span></span></span><br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"><span class="crayon-h"></span><span class="crayon-e">mv </span><span class="crayon-v">GeoLiteCity</span><span class="crayon-sy">.</span><span class="crayon-v">dat</span><span class="crayon-h"> </span><span class="crayon-o">/</span><span class="crayon-v">usr</span><span class="crayon-o">/</span><span class="crayon-v">local</span><span class="crayon-o">/</span><span class="crayon-v">share</span><span class="crayon-o">/</span><span class="crayon-v">GeoIP</span><span class="crayon-o">/</span></span></span><br />
<br />
I recommend you to make a bash script with these commands and run a cron once a month <br />
<br />
<h4>
Required gem</h4>
<br />
To read the legacy GeoIP database I recommend the pure ruby <b>geoip</b> gem. Just include in your Gemfile<br />
<span style="font-family: "Courier New",Courier,monospace;">gem 'geoip'</span><br />
<br />
or<br />
<span style="font-family: "Courier New",Courier,monospace;">gem install geoip</span><br />
<span style="font-family: "Courier New",Courier,monospace;"></span><br />
<h4>
Boilerplate</h4>
The interface of GeoiP gem is really simple however, as we are using a database with less than 100% accuracy, we should account for cases where timezone is not available.<br />
When a ip resolves to a location without timezone, the best trick I found was to try with 'enclosing' networks (bigger supernets) until a result has timezone. That is: first try a perfect match, then try for the network with the last bit of the address masked, then mask 2 bits ... https://en.wikipedia.org/wiki/Subnetwork#Subnet_and_host_counts <br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">Ip2Geo</span>
<span style="color: #003366; font-weight: bold;">DB_PATH</span> <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'/usr/local/share/GeoIP/GeoLiteCity.dat'</span>
<span style="color: #008800; font-weight: bold;">def</span> <span style="color: #0066bb; font-weight: bold;">timezone</span>(ipv4)
<span style="color: #008800; font-weight: bold;">if</span> ipv4<span style="color: #333333;">.</span>present?
addr <span style="color: #333333;">=</span> <span style="color: #003366; font-weight: bold;">IPAddr</span><span style="color: #333333;">.</span>new(ipv4<span style="color: #333333;">.</span>to_s<span style="color: #333333;">.</span>strip)
<span style="color: #0000dd; font-weight: bold;">32</span><span style="color: #333333;">.</span>downto(<span style="color: #0000dd; font-weight: bold;">16</span>)<span style="color: #333333;">.</span>map <span style="color: #008800; font-weight: bold;">do</span> <span style="color: #333333;">|</span>mask<span style="color: #333333;">|</span> <span style="color: #888888;">#if an address fails, test with enclosing network X.X.X.X , X.X.X.0 , X.X.0.0 ...</span>
network <span style="color: #333333;">=</span> addr<span style="color: #333333;">.</span>mask(mask)<span style="color: #333333;">.</span>to_i
c <span style="color: #333333;">=</span> db<span style="color: #333333;">.</span>city(network)
<span style="color: #008800; font-weight: bold;">if</span> c <span style="color: #333333;">&&</span> c<span style="color: #333333;">.</span>timezone
<span style="color: #008800; font-weight: bold;">return</span> c<span style="color: #333333;">.</span>timezone
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #003388; font-weight: bold;">nil</span>
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #888888;">#a similar pattern can be re-used</span>
<span style="color: #008800; font-weight: bold;">def</span> <span style="color: #0066bb; font-weight: bold;">country_code</span>(ipv4)
<span style="color: #008800; font-weight: bold;">if</span> ipv4<span style="color: #333333;">.</span>present?
candidate <span style="color: #333333;">=</span> ipv4<span style="color: #333333;">.</span>to_s<span style="color: #333333;">.</span>strip
addr <span style="color: #333333;">=</span> <span style="color: #003366; font-weight: bold;">IPAddr</span><span style="color: #333333;">.</span>new(ipv4<span style="color: #333333;">.</span>to_s<span style="color: #333333;">.</span>strip)
<span style="color: #0000dd; font-weight: bold;">32</span><span style="color: #333333;">.</span>downto(<span style="color: #0000dd; font-weight: bold;">16</span>)<span style="color: #333333;">.</span>map <span style="color: #008800; font-weight: bold;">do</span> <span style="color: #333333;">|</span>mask<span style="color: #333333;">|</span>
network <span style="color: #333333;">=</span> addr<span style="color: #333333;">.</span>mask(mask)<span style="color: #333333;">.</span>to_i
c <span style="color: #333333;">=</span> db<span style="color: #333333;">.</span>city(network)
<span style="color: #008800; font-weight: bold;">if</span> c <span style="color: #333333;">&&</span> c<span style="color: #333333;">.</span>country_code2
<span style="color: #008800; font-weight: bold;">return</span> c<span style="color: #333333;">.</span>country_code2
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #003388; font-weight: bold;">nil</span>
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #008800; font-weight: bold;">def</span> <span style="color: #0066bb; font-weight: bold;">location</span>(ipv4)
<span style="color: #008800; font-weight: bold;">if</span> ipv4<span style="color: #333333;">.</span>present?
candidate <span style="color: #333333;">=</span> ipv4<span style="color: #333333;">.</span>to_s<span style="color: #333333;">.</span>strip
addr <span style="color: #333333;">=</span> <span style="color: #003366; font-weight: bold;">IPAddr</span><span style="color: #333333;">.</span>new(ipv4<span style="color: #333333;">.</span>to_s<span style="color: #333333;">.</span>strip)
<span style="color: #0000dd; font-weight: bold;">32</span><span style="color: #333333;">.</span>downto(<span style="color: #0000dd; font-weight: bold;">16</span>)<span style="color: #333333;">.</span>map <span style="color: #008800; font-weight: bold;">do</span> <span style="color: #333333;">|</span>mask<span style="color: #333333;">|</span>
network <span style="color: #333333;">=</span> addr<span style="color: #333333;">.</span>mask(mask)<span style="color: #333333;">.</span>to_i
c <span style="color: #333333;">=</span> db<span style="color: #333333;">.</span>city(network)
<span style="color: #008800; font-weight: bold;">return</span> c <span style="color: #008800; font-weight: bold;">if</span> c
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #003388; font-weight: bold;">nil</span>
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #008800; font-weight: bold;">def</span> <span style="color: #0066bb; font-weight: bold;">db</span>
<span style="color: #3333bb;">@db</span> <span style="color: #333333;">||=</span> <span style="color: #003366; font-weight: bold;">GeoIP</span><span style="color: #333333;">.</span>new(<span style="color: #003366; font-weight: bold;">DB_PATH</span>)
<span style="color: #008800; font-weight: bold;">end</span>
<span style="color: #008800; font-weight: bold;">end</span>
</pre>
</div>
<br />
To resolve the ip<br />
<br />
<pre style="line-height: 125%; margin: 0;"><span style="color: #bb0066; font-weight: bold;">locator = Ip2Geo.new</span></pre>
<pre style="line-height: 125%; margin: 0;"><span style="color: #bb0066; font-weight: bold;">locator.</span><span style="color: #0066bb; font-weight: bold;">timezone('193.110.128.199')</span></pre>
<br />
<h4>
Attribution </h4>
Remember that the use of the free database requires an attribution: <i>The attribution requirement may be met by including the following in all
advertising and documentation mentioning features of or use of this
database </i><br />
<i>This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com</i><br />
<br />
<br />
<br />gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com19tag:blogger.com,1999:blog-6931502956661392072.post-38436487231117087782015-06-12T16:35:00.001+02:002015-06-12T16:35:19.850+02:00Canon lide 220 in Ubuntu 14.04Last week I bought a Canon lide 220 scanner. You get a decent device for the price.<br />
<br />
(since I had to read several posts) Here are in a single page all the steps I needed to make it work in Ubuntu 14.04 (via xsane).<br />
<br />
connect the scanner<br />
<br />
ensure it is recognized<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">lsusb</span> <br />
<br />
in teh output, search for the Canon device<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">...</span><br />
<span style="font-family: "Courier New",Courier,monospace;">Bus 003 Device 003: ID 04a9:190f Canon, Inc. </span><br />
<span style="font-family: "Courier New",Courier,monospace;">...</span><br />
<br />
ensure your user has access to the bus (otherwise you'll have to launch <i>everytime</i> xsane as root)<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"> sudo chmod u+w /dev/bus/usb/</span><bus_id><span style="font-family: "Courier New",Courier,monospace;"><bus_id>/<device_id></device_id></bus_id></span><device_id><span style="font-family: "Courier New",Courier,monospace;"><br /></span><br />in my case<br /><br /><span style="font-family: "Courier New",Courier,monospace;"> sudo chmod u+w /dev/bus/usb/003/003</span><br /><br />Then, add this ppa to install latest version of xsane<br /><br /><span style="font-family: "Courier New",Courier,monospace;">sudo add-apt-repository ppa:rolfbensch/sane-git</span><br /><br />and install xsane<br /><br /> <span style="font-family: "Courier New",Courier,monospace;">sudo apt-get update && sudo apt-get install gocr libsane-extras xsane-common xsane</span><br /><br />if you have problems, install a couple of additional packets with<br /><br /> <span style="font-family: "Courier New",Courier,monospace;"> sudo apt-get update && sudo apt-get install gocr libusb-dev libsane-dev libsane-extras xsane-common xsane</span><br /><br />now you can run <br /><br /> <span style="font-family: "Courier New",Courier,monospace;">xsane</span></device_id></bus_id><br />
<br />
<bus_id><device_id> and the scanner is working!</device_id></bus_id><br />
gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com14tag:blogger.com,1999:blog-6931502956661392072.post-66842188202653737132014-12-04T10:17:00.000+01:002014-12-04T10:17:37.709+01:00Quicktip : Remove invalid utf8 charactes in a file Whenever I need to check if a file contains invalid utf8 chars:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">isutf8 file.txt</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
(in ubuntu, you need to install the `moreutils` package)<br />
<br />
Then, to rremove invalid chars, use iconv:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">iconv -f utf-8 -t utf-8 -c nonutf-8.txt > utf8.txt</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: inherit;">-c stands for remove `invalid chars`</span><br />
<span style="font-family: inherit;">-f 'from' utf8</span><br />
<span style="font-family: inherit;">-t 'to' utf8 </span>gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-13104725620341214912014-10-15T10:02:00.001+02:002014-10-15T10:02:38.837+02:00Quick Tip: nginx.conf file for upstart in Ubuntu 14.04It tookme several tries to find the right configuration file for nginx to work with upstart (for example Ubunty 14.04 uses upstart)<br />
<br />
The configuration file is based in the <a href="http://wiki.nginx.org/Upstart" target="_blank">oficial nginx file</a>, but modified to allow restarts (*)<br />
<br />
<br />
<pre style="background: #ffffff; color: black;"><html><body style="background: #ffffff; color: black;"><pre>
<span style="color: dimgrey;"># nginx</span>
description <span style="color: #0000e6;">"nginx http daemon"</span>
author <span style="color: #0000e6;">"George Shammas <georgyo gmail.com="">"</georgyo></span>
start on <span style="color: purple;">(</span>filesystem and net-device-up <span style="color: #797997;">IFACE</span><span style="color: #808030;">=</span>lo<span style="color: purple;">)</span>
stop on runlevel <span style="color: #808030;">[</span><span style="color: #0000e6;">!2345</span><span style="color: #808030;">]</span>
env <span style="color: #797997;">DAEMON</span><span style="color: #808030;">=</span><span style="color: #40015a;">/usr/sbin/nginx</span>
env <span style="color: #797997;">PID</span><span style="color: #808030;">=</span><span style="color: #40015a;">/var/run/nginx.pid</span>
expect fork
respawn
respawn limit <span style="color: #008c00;">10</span> <span style="color: #008c00;">5</span>
pre-start script
<span style="color: #797997;">$DAEMON</span> <span style="color: #44aadd;">-t</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">[</span> <span style="color: #797997;">$?</span> <span style="color: #44aadd;">-ne</span> <span style="color: #008c00;">0</span> <span style="color: #808030;">]</span>
<span style="color: maroon; font-weight: bold;">then</span> <span style="color: #bb7977; font-weight: bold;">exit</span> <span style="color: #797997;">$?</span>
<span style="color: maroon; font-weight: bold;">fi</span>
end script
<span style="color: dimgrey;"># Ensure nginx is shutdown gracefully</span>
<span style="color: dimgrey;"># Upstart will be tracking the wrong PID so the following is needed to stop nginx</span>
post-stop <span style="color: #bb7977; font-weight: bold;">exec</span> start-stop-daemon --stop --pidfile <span style="color: #797997;">$PID</span> --name nginx --<span style="color: #bb7977; font-weight: bold;">exec</span> <span style="color: #797997;">$DAEMON</span> --signal QUIT
<span style="color: #bb7977; font-weight: bold;">exec</span> <span style="color: #797997;">$DAEMON</span> <span style="color: #44aadd;">-c</span> <span style="color: #40015a;">/etc/nginx/conf/nginx.conf</span>
</pre>
---
(*) Without the <span courier="" monospace="" new="" ourier="">post-stop</span> script, nginx didnt kill the forked processed and spit a 502 Bad Gateway .
<span style="font-size: xx-small;"><span courier="" monospace="" new="" ourier="">#you can see taht children are not killed after a `service nginx stop`</span> </span>
<span style="font-size: xx-small;"><span courier="" monospace="" new="" ourier="">sudo netstat -tulpn</span></span>
<span style="font-size: xx-small;"><span courier="" monospace="" new="" ourier="">#to kill stalled children</span></span>
<span style="font-size: xx-small;"><span courier="" monospace="" new="" ourier="">sudo fuser -k 80/tcp</span></span>
</body></html></pre>
gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com1tag:blogger.com,1999:blog-6931502956661392072.post-65612095046220528062014-05-21T13:50:00.001+02:002014-05-21T13:50:18.207+02:00Quick tip: CSS for Hanging indentation<br />Sometimes you need to achieve a fancy effect on the first line of a paragraph, so that just the first line is indented to the left and the other lines be displayed a little bit to the right. Even, I discovered that it has a name 'Hanging Indent':<br /><span style="font-family: "Courier New",Courier,monospace;"><br />This is the first line of a very very very very very<br /> very very very very very very very very very very<br /> long paragraph. And there is more than one sentence.</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> In fact we could reach almost a dozen.</span><br /><br /><br />Just apply a combination of a negative text-indent and padding-left (or margin-left). Ex:<br /><br /><span style="font-family: "Courier New",Courier,monospace;">p {<br /> text-align: left;<br /> text-indent: -25px;<br /> padding-left: 25px;<br />}</span><br />
<br />
There is a 'first-letter' property in css that gives you more customization <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::first-letter" target="_blank">@see this</a>gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-27766938320868563862014-05-19T15:46:00.002+02:002014-05-19T15:46:34.006+02:00Quick tip: optimize images from command lineI use this trick for quickly optimize images. Note, I use ubuntu:<br />
<br />
just install these tools<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">sudo apt-get install <b>optipng jpegoptim</b></span><br />
<br />
and now you can do (beware: input is overwritten) <br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">optipng file.png </span><br />
<span style="font-family: "Courier New",Courier,monospace;">jpegoptim file.jpg</span><br />
<br />
both tools work in the same way<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">jpegoptim [ options ] filenames<br />optipng [options] files</span><br />
<br />
<br />
to convert between formats, you can use imageMagick<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">sudo apt-get install imagemagick</span><br />
<br />
and now<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">convert input.png output.jpg</span><br />
<span style="font-family: "Courier New",Courier,monospace;">convert input.jpg output.png </span><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">Profit! </span>gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-68022518230512520292014-03-23T18:25:00.000+01:002014-03-23T18:26:35.570+01:00Handy enhancement for ruby String: Defaults to default textIn my code , I repeat a lot this pattern: <i>if the string is blank / empty, return a default text, otherwise return the original text</i>.<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">final_text = text.blank? ? "default text" : text</span></span>
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">content_tag :h1, text.blank? ? "default text" : text, :class => 'mega'</span></span><br />
<br />
<br />
I discovered this little gem in Stackoverflow (I lost the original source), you only need to reopen the String class, in an intializer, or decorator.<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">class String</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><br /></span></span>
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"> def defaults_to(what)<br /> self.strip!<br /> self.blank? ? what : self<br /> end</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><br /></span></span>
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">end</span></span><br />
<br />
and, now you can use it in a readable form<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">text.defaults_to("default text")</span></span><br />
<br />
<br />gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-75678546168179943002014-03-01T12:52:00.001+01:002014-03-01T12:53:51.187+01:00Concerns in Rails (or how to reuse code)I have been doing some experiments with rails <a href="http://api.rubyonrails.org/classes/ActiveSupport/Concern.html" target="_blank">Concerns</a> . While they have been widely publicited for Rails 4, you can use them in Rails 3 as well.<br />
<br />
They are like the ruby standard <a href="http://yehudakatz.com/2009/11/12/better-ruby-idioms/" target="_blank">include / extend </a>pattern, but taking care of dependencies. In any case it would be easy to go back to the 'raw' include / exclude game.<br />
<br />
This is an example of how to use them.<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> require 'active_support/concern'<br /><br />module Concerns<br /><br /> module DummyConstants<br /><br /> end<br /><br /> module Dummy<br /> extend ActiveSupport::Concern<br /><br /> #it seems that things declared here are shared between all inclusions (saving memeory)<br /> SEXY_REGEXP = /SEXY_REGEXP/i<br /> MEM_HOGGING = Array.new(1024 * 1024)<br /> MEMBERSHIP_STATUSES = %w(accepted invited requested rejected_by_group rejected_group)<br /><br /> included do<br /> #The included block will be triggered at inclusion time<br /> before_create :stuff_on_creation<br /><br /> attr_accessor :accesor_for_the_instance<br /> class << self<br /> attr_accessor :accesor_for_the_class<br /> end<br /><br /> end<br /><br /> module ClassMethods<br /> #Methods in ClassMethods will get added class methods<br /> def dummy?<br /> puts "\nCLASS dummy? called\n"<br /> true<br /> end<br /><br /> end<br /><br /> #these methods are added as instance methods<br /><br /> def dummy?<br /> puts "\nINSTANCE dummy? called\n"<br /> true<br /> end<br /><br /> def stuff_on_creation<br /> puts "\ncreation called\n"<br /> end<br /><br /> end<br /><br /><br />end</span></span><br />
<br />
Just create a file under model/concerns/dummy.rb and include it<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">User.send :include, Concerns::Dummy</span></span><br />
<br />
or<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">class User</span></span><br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> include Concerns::Dummy</span></span>
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">end</span></span><br />
<br />
Note that I repeat the namespace Concerns, but in other examples found in internet people dont. Rails 4 has included models/concerns and controllers/concerns in the autoload paths and in Rails 4 it would work without the namespace. In Rails 3, you need to add the namespace OR <a href="https://gist.github.com/dhh/1014971" target="_blank">using some trick</a>.<br />
<br />
I find easier to understand if I include the 'Concern', tough.<br />
<br />
Profit!<br />
<br />
<br />
<br />
<br />gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-35093465217067435562014-02-08T11:27:00.000+01:002014-02-08T11:27:09.194+01:00Descubriendo los podcastsUn podcast es basicamente un blog, pero de audio. Y lo realmente
importante es que hay miles de podcasts disponibles, con alta calidad y tratando temáticas muy variadas. Sólo tienes que bajarte los audios (mp3) y escucharlos cuando quieras.<br />
<br />
-- <br />
<br />
Todo se inicia con un tweet de <a href="https://twitter.com/david_bonilla" target="_blank">@david_bonilla </a><br />
"Yo salgo a correr para tener una excusa para escuchar el <a class="twitter-atreply pretty-link" dir="ltr" href="https://twitter.com/amuletodeyendor"><s>@</s><b>amuletodeyendor</b></a>". Asà descubro que "El amuleto de yendor" es un podcast sobre tecnologÃa, dode 2 chavales hablan sobre cosas que me interesan.<br />
<br />
<br />
El prime paso, fue descargarme el último programa desde <a href="http://www.ivoox.com/podcast-amuleto-yendor_sq_f16203_1.html" target="_blank">ivoox</a> : me lo escuche de una sentada pues no tiene desperdicio. ivoox está bien para descargarse programas antiguos, pero se queda corto a la hora de descubrir podcasts similares o buscar ciertas temáticas. Además yo querÃa que me avisaran cuando se grababan episodios nuevos (esto se llama subscripciones, y se maneja mediante rss).<br />
<br />
-- <br />
<br />
Tras un mes de aprendizaje, asà es como gestiono mis podcasts:<br />
<br />
<a href="http://gpodder.net/">gPodder.net</a> es el servicio que me permite guardar mis <b>subscripciones</b> . Es gratuito. Necesitas crear una cuenta y comenzar a <a href="https://gpodder.net/search/" target="_blank">añadir los podcasts</a> que te interesen. <br />
<br />
Lo siguiente es utilizar un programa que te descarge nuevos audios (episodios) según van apareciendo. Asà te ahorras mucha gestion manual.<br />
- para ordenadores tenemos <b>clientes oficiales de gPodder</b> en todas las plataformas <a href="http://gpodder.org/downloads">http://gpodder.org/downloads</a> asà tengo mis WIndows y Linux sincronizados.<br />
- para android yo utilizo <b>ListeUp</b> (no se cual es la diferencia entre la <a href="https://play.google.com/store/apps/details?id=org.codepimps.listenup.free&hl=es" target="_blank">free</a> o la <a href="https://play.google.com/store/apps/details?id=org.codepimps.listenup&hl=es" target="_blank">pro</a> de 1€, pero como es un programa que utilizo mucho, acabé pagando) . Con esto cubro mi MotoG y Nexus7 . Esta aplicacion tiene 2 cosas muy buenas: recuerda el audio que estábas escuchando la última vez que apagaste y que puedes manejar los controles desde la pantalla de bloqueo. También sincroniza con gPodder.<br />
- para el iPad utilizo la aplicacion de <a href="https://itunes.apple.com/es/app/podcasts/id525463029?mt=8" target="_blank">podcasts oficial</a> . No me gusta mucho porque no tiene integracion con gpodder, y tengo que añadir mis subscripciones a mano. De todas formas los podcasts los suelo escuchar en el movil o en ordenador, asi que no he investigado más.<br />
- Creo que VLC tambien puede manejar subscripciones de podcasts, pero no está integrado con gpodder.<br />
- si no te interesa la integracion con gpodder, en android tienes <a href="https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict&hl=es" target="_blank">PodcastAddict</a>, que es una pasada<br />
- si viajas mucho en coche, puedes pasar los episodios a tu reproductor de mp3 y escucharlo en el coche.<br />
<br />
Si utilizas gpodder en varios dispositivos, recuerda gestionar que todos están subscritos a las mismas fuentes y sincronizados, desde https://gpodder.net/devices/ Asi te evitas mucho trabajo.<br />
<br />
--<br />
<br />
Qué escucho?<br />
<br />
Basicamente tecnologÃa: iCharlas, PassionGeek, Infoxicados y el amuleto de Yendor.<br />
<br />
Aqui estan mis subscripciones: en <a href="https://gpodder.net/user/gsusmonzon/subscriptions/rss/" target="_blank">rss</a> , <a href="https://gpodder.net/user/gsusmonzon/subscriptions.opml" target="_blank">opml</a> o directamente la página de <a href="https://gpodder.net/user/gsusmonzon/subscriptions" target="_blank">gpodder</a><br />
<br />
Suelo descubrir nuevos podcasts, navegando por las subscripciones de otros usuarios que estan suscritos a mis mismos podcasts: algunas veces te llevas una sorpresa!<br />
<br />
Dejo un enlace con uno de los episodios que mas me gustaron: Javifrechi (de infoxicados) en iCharlas, <a href="http://icharlas.es/Blog/icharlas-76/" target="_blank">parte1</a> y <a href="http://icharlas.es/Blog/icharlas-77/" target="_blank">parte2</a> <br />
<br />
<br />
<br />
<br />gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-33407324680743033892013-11-13T12:57:00.002+01:002013-11-13T13:00:31.064+01:002 lines that must be on your <head>In few words:<br />
<br />
ensure that these lines are included in your <head>
<!-- HTML generated using hilite.me --><div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #007700"><meta</span> <span style="color: #0000CC">charset=</span><span style="background-color: #fff0f0">"utf-8"</span><span style="color: #007700">/></span>
<span style="color: #007700"><meta</span> <span style="color: #0000CC">http-equiv=</span><span style="background-color: #fff0f0">"X-UA-Compatible"</span> <span style="color: #0000CC">content=</span><span style="background-color: #fff0f0">"IE=edge,chrome=1"</span><span style="color: #007700">/></span>
<span style="color: #007700"><meta</span> <span style="color: #0000CC">name=</span><span style="background-color: #fff0f0">"viewport"</span> <span style="color: #0000CC">content=</span><span style="background-color: #fff0f0">"width=device-width, initial-scale=1.0"</span><span style="color: #007700">/></span>
</pre></div>
gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-84517796683669027902013-09-05T09:49:00.001+02:002013-09-05T10:18:09.416+02:00Take a photo from JavascriptFinally it is 2013 and you can take photos from your browser without requiring <i>flash</i>.<br />
<br />
The API that makes it possible is <span style="font-family: "Courier New",Courier,monospace;">getUserMedia</span> ( <a href="http://caniuse.com/#search=getUserMedia">http://caniuse.com/#search=getUserMedia</a> ) and it is available in all modern browsers (requiring some vendor-prefixes, though).<br />
<br />
For demo: <a href="http://jsfiddle.net/e8ZP3/" target="_blank">use this fiddle</a><br />
<br />
The code I paste here, takes a photo, dump it to a canvas and tries to upload it to a (non-existent) server.<br />
- The key here is that both the captured image AND the uploaded image does not need to be the same size (you usually don't want to upload very big files). That is controlled via the OUTPUT_RATIO constant: the final size is given by the output canvas.<br />
- Neither the video or the output are required to be shown, however is good to give visual feedback to users.<br />
- NOTE: Chrome does not allow local files to get access to getUserMedia. You can use fiddle to make the test yourself.<br />
- At the present, the api still needs to be prefixed depending on the browsers:<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;</span></span><br />
<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #557799;"><!DOCTYPE html></span>
<span style="color: #007700;"><head></span>
<span style="color: #007700;"><meta</span> <span style="color: #0000cc;">charset=</span><span style="background-color: #fff0f0;">"utf-8"</span><span style="color: #007700;">></span>
<span style="color: #007700;"><meta</span> <span style="color: #0000cc;">http-equiv=</span><span style="background-color: #fff0f0;">"X-UA-Compatible"</span> <span style="color: #0000cc;">content=</span><span style="background-color: #fff0f0;">"IE=edge,chrome=1"</span><span style="color: #007700;">></span>
<span style="color: #007700;"><title></span>getUserApi<span style="color: #007700;"></title></span>
<span style="color: #007700;"><meta</span> <span style="color: #0000cc;">name=</span><span style="background-color: #fff0f0;">"description"</span> <span style="color: #0000cc;">content=</span><span style="background-color: #fff0f0;">""</span><span style="color: #007700;">></span>
<span style="color: #007700;"><meta</span> <span style="color: #0000cc;">name=</span><span style="background-color: #fff0f0;">"viewport"</span> <span style="color: #0000cc;">content=</span><span style="background-color: #fff0f0;">"width=device-width"</span><span style="color: #007700;">></span>
<span style="color: #007700;"><style </span><span style="color: #0000cc;">type=</span><span style="background-color: #fff0f0;">"text/css"</span><span style="color: #007700;">></span>
<span style="color: #007700;">video</span> {
<span style="color: #008800; font-weight: bold;">background</span><span style="color: #333333;">:</span> rgba(<span style="color: #6600ee; font-weight: bold;">255</span><span style="color: #333333;">,</span><span style="color: #6600ee; font-weight: bold;">255</span><span style="color: #333333;">,</span><span style="color: #6600ee; font-weight: bold;">255</span><span style="color: #333333;">,</span><span style="color: #6600ee; font-weight: bold;">0</span><span style="color: #333333;">.</span><span style="color: #6600ee; font-weight: bold;">5</span>);
<span style="color: #008800; font-weight: bold;">border</span><span style="color: #333333;">:</span> <span style="color: #6600ee; font-weight: bold;">1px</span> <span style="color: #008800; font-weight: bold;">solid</span> <span style="color: #6600ee; font-weight: bold;">#ccc</span>;
}
<span style="color: #007700;"></style></span>
<span style="color: #007700;"></head></span>
<span style="color: #007700;"><body></span>
<span style="color: #007700;"><div</span> <span style="color: #0000cc;">id=</span><span style="background-color: #fff0f0;">'text'</span><span style="color: #007700;">></span>
<span style="color: #007700;"><p></span>You must grant access to the Camera first.<span style="color: #007700;"></p></span>
<span style="color: #007700;"><p></span>Prompt would be shown above this lines, next to the address bar.<span style="color: #007700;"></p></span>
<span style="color: #007700;"><button</span> <span style="color: #0000cc;">type=</span><span style="background-color: #fff0f0;">'button'</span> <span style="color: #0000cc;">id=</span><span style="background-color: #fff0f0;">'button'</span><span style="color: #007700;">></span>Take photo<span style="color: #007700;"></button></span>
<span style="color: #007700;"></div></span>
<span style="color: #007700;"><video</span> <span style="color: #0000cc;">id=</span><span style="background-color: #fff0f0;">'video'</span> <span style="color: #0000cc;">width=</span><span style="background-color: #fff0f0;">"640"</span> <span style="color: #0000cc;">height=</span><span style="background-color: #fff0f0;">"480"</span><span style="color: #007700;">></video></span>
<span style="color: #007700;"><canvas</span> <span style="color: #0000cc;">id=</span><span style="background-color: #fff0f0;">'canvas'</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"display:none;"</span><span style="color: #007700;">></canvas></span>
<span style="color: #007700;"><script </span><span style="color: #0000cc;">src=</span><span style="background-color: #fff0f0;">"https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.js"</span><span style="color: #007700;">></script></span>
<span style="color: #007700;"><script></span>
(<span style="color: #008800; font-weight: bold;">function</span>(<span style="color: #007020;">window</span>) {
<span style="color: #008800; font-weight: bold;">var</span> nav <span style="color: #333333;">=</span> <span style="color: #007020;">window</span>.navigator,
doc <span style="color: #333333;">=</span> <span style="color: #007020;">window</span>.<span style="color: #007020;">document</span>,
<span style="color: #888888;">//some browsers behave differently</span>
is_webkit <span style="color: #333333;">=</span> nav.webkitGetUserMedia,
is_mozilla <span style="color: #333333;">=</span> nav.mozGetUserMedia,
showSnapshot <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">true</span>,
showVideo <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">true</span>,
OUTPUT_RATIO <span style="color: #333333;">=</span> <span style="color: #6600ee; font-weight: bold;">0.5</span>, <span style="color: #888888;">//the output is X times the captured image (ex: we upload small photos)</span>
source,
video,
canvas,
button,
ctx,
localMediaStream;
<span style="color: #008800; font-weight: bold;">var</span>
initCamera <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">function</span>() {
video <span style="color: #333333;">=</span> <span style="color: #007020;">document</span>.getElementById(<span style="background-color: #fff0f0;">'video'</span>),
canvas <span style="color: #333333;">=</span> <span style="color: #007020;">document</span>.getElementById(<span style="background-color: #fff0f0;">'canvas'</span>),
button <span style="color: #333333;">=</span> <span style="color: #007020;">document</span>.getElementById(<span style="background-color: #fff0f0;">'button'</span>),
ctx <span style="color: #333333;">=</span> canvas.getContext(<span style="background-color: #fff0f0;">'2d'</span>);
<span style="color: #888888;">//make canvas and video the same dimensions</span>
canvas.width <span style="color: #333333;">=</span> video.width <span style="color: #333333;">*</span> OUTPUT_RATIO <span style="color: #333333;">|</span> <span style="color: #0000dd; font-weight: bold;">0</span>;
canvas.height <span style="color: #333333;">=</span> video.height <span style="color: #333333;">*</span> OUTPUT_RATIO <span style="color: #333333;">|</span> <span style="color: #0000dd; font-weight: bold;">0</span>;
<span style="color: #888888;">//turn canvas to visible</span>
canvas.style.display <span style="color: #333333;">=</span> showSnapshot <span style="color: #333333;">?</span> <span style="background-color: #fff0f0;">''</span> <span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'none'</span>;
video.style.display <span style="color: #333333;">=</span> showVideo <span style="color: #333333;">?</span> <span style="background-color: #fff0f0;">''</span> <span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'none'</span>;
(button <span style="color: #333333;">||</span> video).addEventListener(<span style="background-color: #fff0f0;">'click'</span>, takeSnapshot, <span style="color: #008800; font-weight: bold;">false</span>); <span style="color: #888888;">//addEventListener: IE9+ Opera7+ Safari, FFox, Chrome</span>
<span style="color: #888888;">// if (is_webkit){</span>
<span style="color: #888888;">// nav.getUserMedia('video', onSuccess, onError);</span>
<span style="color: #888888;">// }else{</span>
nav.getUserMedia({
video<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">true</span>
}, onSuccess, onError);
<span style="color: #888888;">// }</span>
},
onError <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">function</span>(e) {
alert(<span style="background-color: #fff0f0;">'Camera permission rejected!'</span>, e);
},
onSuccess <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">function</span>(stream) {
<span style="color: #008800; font-weight: bold;">if</span> (is_mozilla) {
source <span style="color: #333333;">=</span> <span style="color: #007020;">window</span>.URL.createObjectURL(stream);
} <span style="color: #008800; font-weight: bold;">else</span> <span style="color: #008800; font-weight: bold;">if</span> (is_webkit) {
source <span style="color: #333333;">=</span> <span style="color: #007020;">window</span>.webkitURL.createObjectURL(stream);
} <span style="color: #008800; font-weight: bold;">else</span> {
source <span style="color: #333333;">=</span> stream;
}
video.src <span style="color: #333333;">=</span> source;
video.play();
localMediaStream <span style="color: #333333;">=</span> stream;
}, stopCamera <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">function</span>(){
localMediaStream.stop();
video.style.display <span style="color: #333333;">=</span> canvas.style.display <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'none'</span>;
localMediaStream <span style="color: #333333;">=</span> canvas <span style="color: #333333;">=</span> ctx <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">null</span>;
button
}, takeSnapshot <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">function</span>() {
<span style="color: #008800; font-weight: bold;">if</span> (localMediaStream) {
ctx.drawImage(video, <span style="color: #0000dd; font-weight: bold;">0</span>, <span style="color: #0000dd; font-weight: bold;">0</span>, (video.width <span style="color: #333333;">*</span> OUTPUT_RATIO) <span style="color: #333333;">|</span> <span style="color: #0000dd; font-weight: bold;">0</span>, (video.height <span style="color: #333333;">*</span> OUTPUT_RATIO) <span style="color: #333333;">|</span> <span style="color: #0000dd; font-weight: bold;">0</span>);
uploadSnapshot();
}
}, uploadSnapshot <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">function</span>(){
<span style="color: #008800; font-weight: bold;">var</span> dataUrl;
<span style="color: #008800; font-weight: bold;">try</span> {
dataUrl <span style="color: #333333;">=</span> canvas.toDataURL(<span style="background-color: #fff0f0;">'image/jpeg'</span>, <span style="color: #0000dd; font-weight: bold;">1</span>).split(<span style="background-color: #fff0f0;">','</span>)[<span style="color: #0000dd; font-weight: bold;">1</span>];
} <span style="color: #008800; font-weight: bold;">catch</span>(e) {
dataUrl <span style="color: #333333;">=</span> canvas.toDataURL().split(<span style="background-color: #fff0f0;">','</span>)[<span style="color: #0000dd; font-weight: bold;">1</span>];
}
$.ajax({
url<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">"localhost:3000/uploadTest"</span>,
type<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">"POST"</span>,
data<span style="color: #333333;">:</span> {imagedata <span style="color: #333333;">:</span> dataUrl}, <span style="color: #888888;">//in the server file.write(Base64.decode64(imagedata)) , https://gist.github.com/pierrevalade/397615</span>
contentType<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">"application/json; charset=utf-8"</span>,
dataType<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">"json"</span>,
success<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">function</span> () {
alert(<span style="background-color: #fff0f0;">'Image Uploaded!!'</span>);
},
error<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">function</span> () {
alert(<span style="background-color: #fff0f0;">"There was some error while uploading Image"</span>);
}
});
};
<span style="color: #888888;">//some browsers use prefixes</span>
nav.getUserMedia <span style="color: #333333;">=</span> nav.getUserMedia <span style="color: #333333;">||</span> nav.webkitGetUserMedia <span style="color: #333333;">||</span> nav.mozGetUserMedia <span style="color: #333333;">||</span> nav.msGetUserMedia;
<span style="color: #008800; font-weight: bold;">if</span> (nav.getUserMedia) {
initCamera();
} <span style="color: #008800; font-weight: bold;">else</span> {
alert(<span style="background-color: #fff0f0;">"Your browser does not support getUserMedia()"</span>)
}
}(<span style="color: #008800; font-weight: bold;">this</span>));
<span style="color: #007700;"></script></span>
<span style="color: #007700;"></body></span>
<span style="color: #007700;"></html></span> </pre>
</div>
<h4 style="line-height: 125%; margin: 0;">
Bonus</h4>
<div style="line-height: 125%; margin: 0;">
For large images you can save some bytes by sending the image as a blob instead of a base64 encoding.</div>
<div style="line-height: 125%; margin: 0;">
<br /></div>
<div style="line-height: 125%; margin: 0;">
First, encode the dataUrl as a blob</div>
<div style="line-height: 125%; margin: 0;">
<br /></div>
<div style="line-height: 125%; margin: 0;">
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">function dataURItoBlob(dataURI, dataTYPE) {<br /> var binary = atob(dataURI), array = [];<br /> for(var i = 0; i < binary.length; i++) array.push(binary.charCodeAt(i));<br /> return new Blob([new Uint8Array(array)], {type: dataTYPE});<br />}</span></span></div>
<div style="line-height: 125%; margin: 0;">
<br /></div>
<div style="line-height: 125%; margin: 0;">
Then, you have 2 options:</div>
<div style="line-height: 125%; margin: 0;">
<br /></div>
<div style="line-height: 125%; margin: 0;">
- using the FormData api</div>
<div style="line-height: 125%; margin: 0;">
<br /></div>
<div style="line-height: 125%; margin: 0;">
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">function uploadWithFormData(dataUrl){<br /> // Get our file<br /> var file = dataURItoBlob(dataUrl, 'image/jpeg'),<br /> fd = new FormData();<br /> // Append our Canvas image file to the form data<br /> fd.append("imageNameHere", file);<br /> // And send it<br /> $.ajax({<br /> url: "/server",<br /> type: "POST",<br /> data: fd,<br /> processData: false,<br /> contentType: false,<br /> });<br />}</span></span></div>
<div style="line-height: 125%; margin: 0;">
<br /></div>
<div style="line-height: 125%; margin: 0;">
- or using the XHR</div>
<div style="line-height: 125%; margin: 0;">
<br /></div>
<div style="line-height: 125%; margin: 0px;">
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">function uploadWithXHR(dataUrl) {<br /> var file = dataURItoBlob(dataUrl, 'image/jpeg'),<br /> xhr = new XMLHttpRequest();<br /> xhr.open('POST', '/server', true);<br /> //add the headers you need<br /> // xhr.setRequestHeader("Cache-Control", "no-cache");<br /> // xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");<br /> // xhr.setRequestHeader("X-File-Name", file.name || file.fileName || 'image.jpg');<br /> // xhr.setRequestHeader("X-File-Size", file.size || file.fileSize);<br /> // xhr.setRequestHeader("X-File-Type", file.type);<br /> // xhr.setRequestHeader("Content-Type", options.type);<br /> // xhr.setRequestHeader("Accept","application/json, text/javascript, */*; q=0.01");<br /> xhr.send(file);<br />}</span></span></div>
gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-51915349442505313912013-08-23T10:15:00.003+02:002013-08-23T10:15:44.646+02:00rubygems: uninstall some gemsI just discovored a way to uninstall only the gems that match a pattern (via <a href="https://coderwall.com/p/lpqmjq" target="_blank">https://coderwall.com/p/lpqmjq</a> )<br />
<br />
<span style="font-family: "Courier New", Courier, monospace;"><span style="font-size: x-small;">gem list [OPTIONAL PATTERN] --no-version | xargs gem uninstall -ax</span></span><br />
<br />
<span style="font-family: "Courier New", Courier, monospace;"><span style="font-size: x-small;"><span style="font-size: small;"><span style="font-family: inherit;">for example</span></span> </span></span><br />
<br />
<span style="font-family: "Courier New", Courier, monospace;"><span style="font-size: x-small;">gem list hobo </span></span><span style="font-family: "Courier New", Courier, monospace;"><span style="font-size: x-small;">--no-version | xargs gem uninstall -ax</span></span><br />
<br />
<span style="font-family: "Courier New", Courier, monospace;"><span style="font-size: x-small;"><span style="font-family: inherit;"><span style="font-size: small;">removes all 'hobo'</span></span> </span></span><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">Successfully uninstalled hobo_jquery_ui-2.0.1<br />Successfully uninstalled hobo_clean_admin-2.0.1<br />Successfully uninstalled hobo_clean-2.0.1<br />Successfully uninstalled hobo_bootstrap_ui-2.0.1<br />Successfully uninstalled hobo_bootstrap-2.0.1<br />Successfully uninstalled hobo_jquery-2.0.1<br />Successfully uninstalled hobo_rapid-2.0.1<br />Removing hobo<br />Successfully uninstalled hobo-2.0.1<br />Removing hobofields<br />Successfully uninstalled hobo_fields-2.0.1</span></span>gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-81873188519252989252013-08-11T19:19:00.003+02:002013-08-11T19:19:47.249+02:00JS: detect unsaved changes in a formHere is a small jQuery plugin to detect changes on a form.<br />
<a href="http://www.blogger.com/goog_454679148"><br /></a>
<a href="https://github.com/gsusmonzon/jquery.simple.unsaved">https://github.com/gsusmonzon/jquery.simple.unsaved</a><br />
<br />
The tricky part is to store a hash of the serialization string instead of the full serialized form. The rest is not worth mentioning.gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-16312218919410036532013-07-26T12:42:00.005+02:002013-07-26T12:43:31.612+02:00Solving ActiveRecord::ReadOnlyRecord in Rails 3<br />
When you pass an SQL fragment to a finder, join or named scope, ActiveRecord returns read-only results by default.<br />
<br />
Use <span style="font-family: "Courier New",Courier,monospace;">readonly(false)</span> in your queries to force that results are writable. Ex:<br />
<br />
(Rails 3)<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">User.joins("INNER JOIN `cars` ON `cars`.`user_id` = `users`.`id` AND `cars`.`colour` = 'electric blue'").<b>readonly(false)</b></span></span><br />
<br />gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com1tag:blogger.com,1999:blog-6931502956661392072.post-30283783050940968422013-07-21T11:16:00.000+02:002013-07-21T11:16:22.257+02:00Backup with Duplicity and Rackspace Cloud Files<h3>
Intro</h3>
<br />
<a href="http://duplicity.nongnu.org/" target="_blank">Duplicity</a> is a Linux tool for making backups of files an folders<br />
<ul>
<li>supports <i>full</i> and <i>incremental</i> backups</li>
<li>supports <i>encryption</i>, by using GPG. ( You can have unnencrypted backups as well )</li>
<li>supports for many kinds of storage scp, rsync, Amazon S3 or Rackspace Cloud Files</li>
</ul>
<br />
My current setup is to make daily incremental backups and store them into <i>Rackspace Cloud Files</i>. <br />
Once a fortnight you can do a full backup, and clean older backups (30 days old).<br />
To use other storages you only need to change the last part of this guide.<br />
<br />
During this article:<br />
<br />
- <b>Machine L</b> is your local and personal machine. L == local<br />
- <b>Machine B</b> is the machine with the data that we want to backup. B == backedup. Duplicity runs in this machine. I use Ubuntu 12.04 here.<br />
- <b>Machine S</b> is the remote machine where we'll store the backup. S == storage<br />
<h3>
</h3>
<h3>
Step 1: Generate the encryption keys</h3>
<b>We'll generate the keys in our local machine and export them to backup machine</b><br />
<br />
( from <a href="http://www.debian-administration.org/articles/209">http://www.debian-administration.org/articles/209</a> )<br />
<br />
We'll need <b>two gpg</b> keys for our backups <br />
<br />
- <b>encryption key</b> : the encryption key is used to protect the data in the backup files from snooping on the backup server<br />
- <b>signature key</b> : the signature key is used to ensure the integrity of the backup files.<br />
<br />
<i>The private key for the signature key must be available to duplicity when it runs. Duplicity also requires the passphrase for the signing key be either entered manually or stored in an environment variable. (that means in the Machine B) If our encryption key and signature key are the same, then a compromise of the server means a compromise of the backed up data as well. We'll therefore use separate encryption and signature keys.</i><br />
<br />
In your local machine, Machine L<br />
<br />
<span style="font-family: "Courier New", Courier, monospace;">sudo apt-get install gnupg</span><br />
<br />
<h4>
generate encyption key</h4>
(in your local Machine L)<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">gpg --gen-key<br /><br /> (and pick the default options: RSA & RSA + 4096 + never expires)<br /> passphrase: this is the encryption passphrase</span></span><br />
<br />
with a result of<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><br /> gpg: key <b>5A87AAB8</b> marked as ultimately trusted<br /> public and secret key created and signed.</span></span><br />
...<br />
<br />
<br />
Do the same to generate your signature key, use a different paraphrase.<br />
(in your local Machine L)<br />
<h4>
generate signature key</h4>
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">gpg --gen-key<br /><br /> (and pick the default options: RSA & RSA + 4096 + never expires)<br /> passphrase: this is the sign passphrase</span></span><br />
...<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><br /> gpg: key </span></span><span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><b>927AE728</b></span></span> marked as ultimately trusted<br /> public and secret key created and signed.</span></span><br />
<br />
To check that everything went well:<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">gpg --list-keys && gpg --list-secret-keys</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">/home/jesus/.gnupg/pubring.gpg<br />------------------------------<br />pub 4096R/<b>5A87AAB8</b> 2013-07-07<br />uid backuper-<b>encrypt</b> (Backup with duplicity) <bob example.com=""><br />sub 4096R/11122AE7 2013-07-07<br /><br />pub 4096R/<b>927AE728</b> 2013-07-07<br />uid backuper-<b>signature</b> (Signature with duplicity) <bob example.com=""><br />sub 4096R/10E7002A 2013-07-07<br /><br />/home/jesus/.gnupg/secring.gpg<br />------------------------------<br />sec 4096R/5A87AAB8 2013-07-07<br />uid backuper-encrypt (Backup with duplicity) <bob example.com=""><br />ssb 4096R/11122AE7 2013-07-07<br /><br />sec 4096R/927AE728 2013-07-07<br />uid backuper-signature (Signature with duplicity) <bob example.com=""><br />ssb 4096R/10E7002A 2013-07-07</bob></bob></bob></bob></span></span><br />
<h4>
trust your keys before exporting</h4>
Now we are going to <i>trust</i> the keys before <i>exporting</i> them.<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">gpg --edit-key <b>927AE728</b><br /> > trust<br /> > 5<br /> > save<br /><br />gpg --edit-key <b>5A87AAB8</b><br /> > trust<br /> > 5<br /> > save</span></span><br />
<br />
and sign keys <br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">gpg --sign-key 927AE728</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">gpg --sign-key 5A87AAB8</span></span> (not sure if this one is needed)<br />
<br />
Once both keys have been created you need to <b>export and copy the public encryption and private signature keys</b> to the Machine B the safest way to do this is SCP/SSH ( (you'll need ssh access). <b>You MUST keep safe and private the private encryption key and its paraphrase.</b><br />
<br />
(in your local Machine L)<br />
change the ip for the Machine B<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">cd /tmp </span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">gpg --export -a 5A87AAB8 > backup.enc.pub.gpg<br />gpg --export-secret-keys -a 927AE728 > backup.sig.sec.gpg<br />gpg --export-ownertrust > backup.trust</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">scp backup.enc.pub.gpg backup.sig.sec.gpg backup.trust bob@192.168.33.10:/tmp</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">rm backup.*</span></span><br />
<br />
<h3>
Import keys in the backup server</h3>
Our backups are handled by <b>root</b> (full access to everything, and to keep signature passphrase private) so we need to configure duplicity logged as root in the Machine B. <br />
(in machine b)<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">sudo su</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">sudo apt-get install gnupg</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><br /></span></span>
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">cd /tmp</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">gpg --import /tmp/backup.sig.sec.gpg /tmp/backup.enc.pub.gpg<br />gpg --import-ownertrust /tmp/backup.trust</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">rm backup.*</span></span><br />
<br />
Verify the keys were imported correctly. Check that the ID's are correct. The private encryption key was not transferred, so we expect only one entry for the secret keys.<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">gpg --list-keys && gpg --list-secret-keys<br /><br />/root/.gnupg/<b>pub</b>ring.gpg<br />------------------------<br />pub 4096R/<b>927AE728</b> 2013-07-07<br />uid backuper-signature (Signature with duplicity) <bob example.com=""><br /><br /><br />pub 4096R/<b>5A87AAB8</b> 2013-07-07<br />uid backuper-encrypt (Backup with duplicity) <bob example.com=""><br /><br /><br />/root/.gnupg/<b>sec</b>ring.gpg<br />------------------------<br />sec 4096R/<b>927AE728</b> 2013-07-07<br />uid backuper-signature (Signature with duplicity) <bob example.com=""><br /></bob></bob></bob></span></span><br />
<br />
<br />
Note: If you didnt used the import ownertrust, trust the private key ( <a href="http://www.linuxforums.org/forum/servers/153552-solved-export-gpg-keys-multiple-servers.html" target="_blank">in case of untrusted key errors while running duplicity</a> )<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">gpg --edit-key <b>927AE728</b><br /> > trust<br /> > 5<br /> > save</span></span><br />
<br />
<h3>
Step 2: configure duplicity to use Cloud Files</h3>
<a href="http://duplicity.nongnu.org/" target="_blank">Install duplicity.</a> I use the latest version, which is not included by default in <b>Ubuntu</b>. I prefer to add a ppa for it and run the install via apt.<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">sudo apt-get -y install python-software-properties && sudo add-apt-repository -y ppa:duplicity-team/ppa && sudo apt-get -y update && sudo apt-get -y upgrade</span></span><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">sudo apt-get -y install duplicity python-paramiko</span></span><br />
<h4>
Adding cloudfiles support </h4>
This step is only required if you are going to store backups on Rackspace Cloud Files. You'll find a lot more tutorials for using Amazon S3 .<br />
To store backups in a remote server via scp or rsync it is even easier, and you did the hard part Jump to next step.<br />
<br />
There are 2 ways of using cloudfiles, I use the new pyrax API. The old python-cloudfiles is now deprecated. Choose what works best for you.<br />
<h4>
</h4>
<h4>
option A ) using the new pyrax API</h4>
it is the official way, but as of July'13 it requires more manual tunning<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">sudo apt-get -y install python-pip python-dev build-essential<br />yes | sudo pip install pyrax && yes | sudo pip uninstall keyring<br />sudo apt-get -y install duplicity python-paramiko gnupg</span></span><br />
<br />
In July'13 teh backend needed for pyrax is missing in duplicity. Then we need to copy the new backend ourselves (at the present the backend for cfpyrax+http:// is missing). A backend is a 'module' that tells duplicity how to work with a storage like scp, rsync, s3, etc.<br />
(remember we are root)<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">cd /tmp<br />wget https://bugs.launchpad.net/duplicity/+bug/1179322/+attachment/3735776/+files/pyraxbackend.py<br />sudo chown root:root pyraxbackend.py<br />sudo mv pyraxbackend.py /usr/share/pyshared/duplicity/backends/</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><br />sudo ln -s /usr/share/pyshared/duplicity/backends/pyraxbackend.py /usr/lib/python2.7/dist-packages/duplicity/backends/pyraxbackend.py</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><br />python -m compileall /usr/lib/python2.7/dist-packages/duplicity/backends</span></span><br />
<br />
(in my machine B it was on python2.7/dist-packages, in yours, you can make a `<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">sudo find / -name backends</span></span>` to find where to link to )<br />
<br />
these steps enable duplicity to understand the scheme cfpyrax+http://<br />
Note that it uses https even the scheme reads just http.<br />
<br />
<h4>
option B) using the deprecated python-cloudfiles api</h4>
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">sudo apt-get -y install python-stdeb</span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">sudo pypi-install python-cloudfiles<br />sudo apt-get -y install duplicity python-paramiko</span></span><br />
<br />
these installs enable duplicity to understand the scheme cf+http://<br />
Note that it uses https even the scheme reads just http.<br />
<h3>
Step3: script for making the backups</h3>
In machine B, we set a cron task that runs daily. It runs as root and uses duplicity to make a backup and copy it to Cloud Files (or the destination Machine S)<br />
<br />
A base script for cloud files could be<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #996633;">CLOUD_CONTAINER</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"bob_backup"</span> </pre>
<pre style="line-height: 125%; margin: 0;"><span style="color: #007020;">#required for CLOUD FILES SUPPORT </span></pre>
<pre style="line-height: 125%; margin: 0;"><span style="color: #007020;">export </span><span style="color: #996633;">CLOUDFILES_USERNAME</span><span style="color: #333333;">=</span>my_username
<span style="color: #007020;">export </span><span style="color: #996633;">CLOUDFILES_APIKEY</span><span style="color: #333333;">=</span>4534534543543sd43434546456
<span style="color: #007020;">export </span><span style="color: #996633;">CLOUDFILES_REGION</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"ORD"</span></pre>
<pre style="line-height: 125%; margin: 0;"><span style="background-color: #fff0f0;"> </span></pre>
<pre style="line-height: 125%; margin: 0;"><span style="color: #007020;">#required for duplicity </span></pre>
<pre style="line-height: 125%; margin: 0;"><span style="color: #007020;">export </span><span style="color: #996633;">PASSPHRASE</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"passphrase for the sign key"</span>
<span style="color: #007020;">export </span><span style="color: #996633;">SIGN_PASSPHRASE</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"passphrase for the sign key"</span> </pre>
<pre style="line-height: 125%; margin: 0;"></pre>
<pre style="line-height: 125%; margin: 0;"><span style="color: #996633;">options</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"--full-if-older-than 15D --volsize 250 --exclude-other-filesystems --sign-key 927AE728 --encrypt-key 5A87AAB8"</span>
duplicity <span style="color: #996633;">$options</span> /var/log cfpyrax+http://<span style="color: #008800; font-weight: bold;">${</span><span style="color: #996633;">CLOUD_CONTAINER</span><span style="color: #008800; font-weight: bold;">}</span>
<span style="color: #007020;">unset </span>PASSPHRASE
<span style="color: #007020;">unset </span>SIGN_PASSPHRASE
<span style="color: #007020;">unset </span>CLOUDFILES_APIKEY
</pre>
</div>
<br />
Note how duplicity is instructed to use the 2 keys and you pass the passhphrase of the signing key (this is safe since you need the private key of the encryption key AND its passphrase)<br />
<br />
Duplicity generates 3 files (data, metadata and signature) each time it runs. These files will appear in your Cloud Files container.<br />
<br />
As we are using the Cloud Files pyrax API, we use a cfpyrax+http:// uri. Change the usri scheme to cf+http:// for the old API.<br />
If you back up to a server via scp or rsync change this remote uri accordingly.<br />
<br />
For amazon, read <a href="http://spin.atomicobject.com/2012/06/14/encrypted-offsite-backups-with-duplicity/" target="_blank">this</a>.<br />
<br />
To avoid your backups grows too much,, add something like this at the end of the script<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"># Delete duplicity backups older than 30 days. <br />duplicity <b>remove-older-than</b> 30D --sign-key 927AE728 --encrypt-key 5A87AAB8 cfpyrax+http://${CLOUD_CONTAINER}<br /></span></span><br />
<h4>
Verify the encryption.</h4>
To check that everything went well, we can tell duplicity to check the status of the backup. We can do if from our machine B and the command to use is:<br />
(emember to export all the cloudfiles variables first, as in the previous script)<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">duplicity <b>collection-status</b> --sign-key 927AE728 --encrypt-key 5A87AAB8 cfpyrax+http://${CLOUD_CONTAINER}</span></span><br />
<br />
It will list all your backups and a comforting "No orphaned or incomplete backup sets found" <br />
<br />
<h4>
Testing the recovery</h4>
We need the private encryption key and its passphrase. Remember that we kept it in our private Machine L. If you lost them, you wont be able to recover your backup.<br />
<br />
Move to your <b>local</b> Machine where private keys are available, and install duplicity (and the support for Cloud Files: step 2).<br />
<br />
To do a restore, you need to run the duplicity command with the restore option. You will be prompted for a passhphrase. This time use the <b>encryption</b> <b>passhphrase</b>.<br />
<br />
The command is<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"> duplicity [restore] [options] source_url target_dir</span></span><br />
but 'restore' is optional. Duplicity knows that we are restoring since the remote url comes before a local directory. When the url is the last parameter, duplicity does a backup.<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #888888;">#/bin/bash</span>
<span style="color: #888888;"># note, to run this script you need to have imported the PRIVATE KEY used for encryption</span>
<span style="color: #888888;"># gpg --import /tmp/backup.sig.sec.gpg /tmp/backup.enc.pub.gpg </span>
<span style="color: #888888;"># and you must know the passphrase for the encryption key</span>
<span style="color: #996633;">DST_FOLDER</span><span style="color: #333333;">=</span>/tmp/restored_files
mkdir -p <span style="color: #996633;">$DST_FOLDER</span>
<span style="color: #996633;">CLOUD_CONTAINER</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"bob_backup"</span>
<span style="color: #888888;">#required for CLOUD FILES SUPPORT </span>
<span style="color: #007020;">export </span><span style="color: #996633;">CLOUDFILES_USERNAME</span><span style="color: #333333;">=</span>my_username
<span style="color: #007020;">export </span><span style="color: #996633;">CLOUDFILES_APIKEY</span><span style="color: #333333;">=</span>4534534543543sd43434546456
<span style="color: #007020;">export </span><span style="color: #996633;">CLOUDFILES_REGION</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"ORD"</span>
<span style="color: #888888;"># no passphrase provided, so we'll be asked interactively</span>
<span style="color: #996633;">options</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"--sign-key 927AE728 --encrypt-key 5A87AAB8 --volsize 250"</span>
duplicity <span style="color: #996633;">$options</span> cfpyrax+http://<span style="color: #008800; font-weight: bold;">${</span><span style="color: #996633;">CLOUD_CONTAINER</span><span style="color: #008800; font-weight: bold;">}</span> <span style="color: #996633;">$DST_FOLDER</span>
<span style="color: #007020;">unset </span>CLOUDFILES_APIKEY
</pre>
</div>
<br />
The verify command is another useful command (in this Machine L)<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">duplicity verify [options] source_url target_dir</span></span><br />
<br />
<h3>
Step 4) Finishing</h3>
Just remember to keep your encryption key & passphrase safe, and to check on a regular basis your backups.<br />
<br />
<h4>
Sources</h4>
<a href="http://27smiles.com/2010/04/07/securely-backup-of-vps-with-duplicity-and-gpg/">http://27smiles.com/2010/04/07/securely-backup-of-vps-with-duplicity-and-gpg/</a><br /><a href="http://spin.atomicobject.com/2012/06/14/encrypted-offsite-backups-with-duplicity/">http://spin.atomicobject.com/2012/06/14/encrypted-offsite-backups-with-duplicity/</a><br /><a href="http://www.blogger.com/"><span id="goog_1675997705"></span>http://www.debian-administration.org/articles/209</a><span id="goog_1675997706"></span><br />for integration with cloud files<br />
<br /><a href="http://www.uno-code.com/?q=node/184">http://www.uno-code.com/?q=node/184</a><br /><a href="http://blog.chmouel.com/2011/01/06/backup-with-duplicity-on-rackspace-cloudfiles-including-uk-script/" target="_blank">http://blog.chmouel.com/2011/01/06/backup-with-duplicity-on-rackspace-cloudfiles-including-uk-script/</a><br />
<br />gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com5tag:blogger.com,1999:blog-6931502956661392072.post-70182747881892528212013-05-20T13:22:00.000+02:002013-05-20T13:23:16.994+02:00Mysql Fix Illegal mix of collationsIf you get an error like this while using MYSQL<br />
<br />
Mysql2::Error: Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation<br />
<br />
this is probably due to using or mixing different collations in a select: in my case I was joining columns with different collations. How to fix that:<br />
<br />
I set prudent defaults to my database so that it wont happens again:<br />
<span style="font-family: "Courier New",Courier,monospace;">mysql>></span><br />
<span style="font-family: "Courier New",Courier,monospace;">ALTER DATABASE `<b>database_name</b>` CHARACTER SET utf8 COLLATE utf8_unicode_ci;</span><br />
<span style="font-family: "Courier New",Courier,monospace;">ALTER DATABASE `<b>database_name</b>` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci;</span><br />
<br />
or in a rails migration<br />
<span style="font-family: "Courier New",Courier,monospace;">execute("ALTER DATABASE `#{ActiveRecord::Base.connection.current_database}` CHARACTER SET utf8 COLLATE utf8_unicode_ci;")</span><br />
<span style="font-family: "Courier New",Courier,monospace;">execute("ALTER DATABASE `#{ActiveRecord::Base.connection.current_database}` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci;")</span><br />
Then fix the tables you need to. If you dont know what are all problematic tables, ask the db:<br />
<span style="font-family: "Courier New",Courier,monospace;">SELECT table_schema, table_name, column_name, character_set_name, collation_name FROM information_schema.columns WHERE table_schema = '<b>database_name</b>' AND collation_name <> 'utf8_unicode_ci' ORDER BY table_schema, table_name,ordinal_position;</span><br />
or <br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">SELECT table_name FROM information_schema.columns WHERE table_schema = '<b>databse_name</b>' AND collation_name <> 'utf8_unicode_ci' GROUP BY table_name;</span><br />
then for each table <br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">ALTER TABLE <b>table_name</b> CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;</span><br />
<br />
<br />
<br />
You can put all of this stuff in a single Rails migration.<br />
<br />
Profit!gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-71695274502081008892013-05-05T11:01:00.002+02:002013-05-05T17:46:21.490+02:00Testing with Robolectric in AndroidThis is a quick guide of how I set up a testing environment for our Android application. <br />
<br />
We use Robolectric and Mockito instead of the android tools. The key benefit of this setup is the *speed*: tests run in a Java Project, bypassing the emulator (android tools make tests run in the emulator).<br />
<h4>
Maven: not today</h4>
<a href="http://pivotal.github.io/robolectric/index.html" target="_blank">Robolectric</a> documentation advices to install it thorugh maven. However I was unable to mavenize our project. In fact, making Eclipse (we use eclipse) to play nice with maven corrupted my ADT twice. So, we will <b>use robolectric without maven</b>.<br />
<h4>
Preparation</h4>
Make sure that your <b>android tools</b> are in the system <b>path</b>. Open a terminal, if <span style="font-family: "Courier New", Courier, monospace;">android -h<span style="font-family: inherit;"> is not recognized then you have to</span></span><span style="font-family: "Courier New", Courier, monospace;"><span style="font-family: inherit;"> get the path of yout tools, and the update your <span style="font-family: "Courier New",Courier,monospace;">.profile</span> or <span style="font-family: "Courier New",Courier,monospace;">.bashrc</span> </span></span>with somelink like these lines at the end (use your path):<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #906030;">ANDROID_HOME</span><span style="color: #303030;">=</span>/home/jesus/bin/adt-bundle-linux-x86_64/sdk
<span style="color: #007020;">export </span>ANDROID_HOME
<span style="color: #906030;">PATH</span><span style="color: #303030;">=</span><span style="color: #906030;">$PATH</span>:<span style="color: #906030;">$ANDROID_HOME</span>/tools:<span style="color: #906030;">$ANDROID_HOME</span>/platform-tools
<span style="color: #007020;">export </span>PATH</pre>
<pre style="line-height: 125%; margin: 0;"> </pre>
</div>
reload your profile:
<code>
source ~/.profile </code>
<br />
<br />
I recommend you to download the source code of Robolectric from <a href="https://github.com/pivotal/robolectric">https://github.com/pivotal/robolectric</a> and the sample project and a sample project <a href="https://github.com/pivotal/RobolectricSample">https://github.com/pivotal/RobolectricSample</a> . I use them as <b>documentation</b> when official fall short.<br />
<br />
Note that we can use the pom.xml in the robolectric project ( <a href="https://github.com/pivotal/robolectric/blob/master/pom.xml">https://github.com/pivotal/robolectric/blob/master/pom.xml</a> ) to know which versions of each library are safe to use when downloading the dependencies. I call it the <i>pom trick</i>.<br />
<h4>
Eclipse project</h4>
This is key to understand. Robolectric runs as a java application, not as an android application. So our test project will be a Java project, not an android project or an android test project. Our run configuration will be a Java junit configuration, not an android test run configuration. We see it now.<br />
<br />
Create a new<b> Java projec</b>t <i>File >New > Java Project </i>. I name it as my Android project to test + 'Test'. For example Microhealth and MicrohealthTest. This new project will be our <i>test project</i> vs the <i>android project.</i> Finish.<i><br /></i><br />
<br />
<br />
Create a folder called <i>libs</i> where we put the <b>libraries</b> we need to run robolectric. I usually do it in the file explorer and the I press 'Refresh F5' in eclipse. What libraries do we need? It might change with newer versions of robolectric, but at least:<br />
- <b>Robolectric</b>. Get it from <a href="http://pivotal.github.io/robolectric/download.html">http://pivotal.github.io/robolectric/download.html</a> ( that in turn redirects to <a href="https://oss.sonatype.org/index.html#nexus-search;quick~robolectric" target="_blank">sonatype</a> ) Download the latest <span style="font-family: "Courier New",Courier,monospace;">robolectric-X.X.X-jar-with-dependencies.jar</span> In my case I using robolectric-2.0-alpha3-jar-with-dependencies.<br />
- <b>Junit 4</b>: We need Junit 4 from <a href="http://junit.org/">http://junit.org/</a> Not all versions are compatible with Robolectric. I am using now junit-4.10.jar and discarded newer versions. (or use the <i>pom trick</i> i described before)<br />
- <b>Mockito</b>: get mockito-all-1.95.jar from <a href="http://code.google.com/p/mockito/downloads/list" target="_blank">http://code.google.com/p/mockito/downloads/list</a><br />
- <b>android.jar</b>: get it from your android installation got to your sdk_root in <span style="font-family: "Courier New",Courier,monospace;"><i>sdk root</i>/platforms/android-9/android.jar </span>(I am using the 9 as the min version, change it to yours)<br />
- in case you need maps: get <b>maps.jar</b> from<span style="font-family: "Courier New",Courier,monospace;"> sdk root/add-ons/addon-google_apis_google-9/libs/maps.jar </span><span style="font-family: inherit;">I usually skip this part.</span><br />
<span style="font-family: inherit;"> </span>- <b>FEST</b> libs. These are required by robolectric to make writing tests less verbose. It tookme some time until I get the right versions of FEST but you can use the <i>pom trick</i> as well. <b>fest-assert-core-2.0M10.jar and fest-util-1.2.5.jar</b> (download from http://mvnrepository.com/artifact/org.easytesting/fest-assert-core/2.0M10 )<br />
- hamcrest-all: hamcrest-all-1.3.jar from <a href="http://code.google.com/p/hamcrest/downloads/list">http://code.google.com/p/hamcrest/downloads/list</a> <br />
<br />
Once you have all of them in your test project /libs folder. Declare you wnat to use them: right click on the test project > <i>Properties > Java build path > Libraries > Add Jars</i> and add them.<br />
<br />
<b>Make sure</b> that robolectric and its dependencies (including JUnit) appear before the Android API jars in the classpath. In the properties > Order and export > move the android.jar and maps.jar after all other libraries<br />
<br />
<br />
<b>Require you android project in the build path</b>. Make sure than <i>Properties > Java build path > Projects</i>, references to you android project (Add > your android project)<br />
<br />
Thats all. Now are going to test our setup.<br />
<br />
(I found the official guide a bit outdated <a href="http://pivotal.github.io/robolectric/eclipse-quick-start.html">http://pivotal.github.io/robolectric/eclipse-quick-start.html</a> , but maybe it works better with older robolectric versions)<br />
<h4>
Our first test</h4>
create a new class in the test project. This will contain out first test. Something like:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: green; font-weight: bold;">package</span> com<span style="color: #303030;">.</span><span style="color: #0000c0;">microhealth</span><span style="color: #303030;">.</span><span style="color: #0000c0;">test</span><span style="color: #303030;">.</span><span style="color: #0000c0;">testicle</span><span style="color: #303030;">;</span>
<span style="color: grey;">//Let's import Mockito statically so that the code looks clearer</span>
<span style="color: green; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">static</span> org<span style="color: #303030;">.</span><span style="color: #0000c0;">junit</span><span style="color: #303030;">.</span><span style="color: #0000c0;">Assert</span><span style="color: #303030;">.*;</span>
<span style="color: green; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">static</span> org<span style="color: #303030;">.</span><span style="color: #0000c0;">mockito</span><span style="color: #303030;">.</span><span style="color: #0000c0;">Mockito</span><span style="color: #303030;">.*;</span>
<span style="color: green; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">org.junit.Before</span><span style="color: #303030;">;</span>
<span style="color: green; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">org.junit.Test</span><span style="color: #303030;">;</span>
<span style="color: green; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">org.junit.runner.RunWith</span><span style="color: #303030;">;</span>
<span style="color: green; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">org.robolectric.RobolectricTestRunner</span><span style="color: #303030;">;</span>
<span style="color: green; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">android.app.Activity</span><span style="color: #303030;">;</span>
<span style="color: green; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">android.content.Context</span><span style="color: #303030;">;</span>
<span style="color: green; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">android.os.Bundle</span><span style="color: #303030;">;</span>
<span style="color: green; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">android.util.Log</span><span style="color: #303030;">;</span>
<span style="color: #505050; font-weight: bold;">@RunWith</span><span style="color: #303030;">(</span>RobolectricTestRunner<span style="color: #303030;">.</span><span style="color: #0000c0;">class</span><span style="color: #303030;">)</span>
<span style="color: green; font-weight: bold;">public</span> <span style="color: green; font-weight: bold;">class</span> <span style="color: #b00060; font-weight: bold;">FooTest</span> <span style="color: #303030;">{</span>
<span style="color: #505050; font-weight: bold;">@Test</span>
<span style="color: green; font-weight: bold;">public</span> <span style="color: #303090; font-weight: bold;">void</span> <span style="color: #0060b0; font-weight: bold;">testDummmy</span><span style="color: #303030;">()</span> <span style="color: green; font-weight: bold;">throws</span> Exception <span style="color: #303030;">{</span>
assertTrue<span style="color: #303030;">(</span><span style="color: green; font-weight: bold;">true</span><span style="color: #303030;">);</span>
<span style="color: #303030;">}</span>
<span style="color: #505050; font-weight: bold;">@Before</span>
<span style="color: green; font-weight: bold;">public</span> <span style="color: #303090; font-weight: bold;">void</span> <span style="color: #0060b0; font-weight: bold;">setUp</span><span style="color: #303030;">()</span> <span style="color: green; font-weight: bold;">throws</span> Exception <span style="color: #303030;">{</span>
<span style="color: grey;">//no need to setup nothing</span>
<span style="color: #303030;">}</span>
<span style="color: #303030;">}</span>
</pre>
</div>
<br />
The test MUST be run as a Junit test NOT as an android test. Go to <i>Run > Run Configurations</i> menu, and create a new JUnit test configuration (the name is not important). Do <b>not</b>
create an ‘Android JUnit Test’.<br />
<ul>
<li>Set the <b><i>test runner</i></b> as JUnit 4. (tab test)</li>
<li>check the '<b>Run all tests in the selected project</b>,
package or source folder' and choose your <i>test project</i>. (not the android project)</li>
<li>Locate at the bottom, the link: <i>Multiple launchers available — Select one…</i>. Click the <b>Select other…</b> link, check <i>Use configuration specific settings</i> and
choose <i><b>Eclipse JUnit Launcher</b>. </i>Remember taht the test project runs as a Java project, we dont want it to run as an Android one.</li>
<li>in the 'Arguments' tab, configure the <b><i>working directory</i></b> as the <b>android directory</b>. In the 'Working directory' section, check 'Other' > 'Workspace' and locate your android project. Select it.</li>
</ul>
Click on 'Run' to save and make your tests run. If everything is ok, your first test run and pass. <br />
<br />
<br />
I write a future post I will explain how to set up tests to run a project that depends on DataDroid and ActionBarSherlock. <br />
<br />gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com7tag:blogger.com,1999:blog-6931502956661392072.post-44934972330047252852013-03-05T13:11:00.002+01:002013-03-05T13:11:51.562+01:00Autologin and lock in Ubuntu 12.10 It is very handy to set your computer to log into your account and then lock the screen. And it is very easy to set up in Ubuntu (I am using Ubuntu 12.10 w Unity).<br />
<br />
1.- Set your screen saver to lock your station:<br />
Settings > Brightness and lock and set on the lock options:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2oacCWAvico_6RSxSarn3wIwTnhcnZY3DexPoy5372nXfAzwFpyMxEXJiTwZXqXo9XVZSvDNvlPOFxX8Fp-NyQI_83wphyphenhyphenW0KBGOnPaabsZTUphYj2r1S23lRoUYokRRz0qoeabgJgjQX/s1600/lock_screen.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="139" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2oacCWAvico_6RSxSarn3wIwTnhcnZY3DexPoy5372nXfAzwFpyMxEXJiTwZXqXo9XVZSvDNvlPOFxX8Fp-NyQI_83wphyphenhyphenW0KBGOnPaabsZTUphYj2r1S23lRoUYokRRz0qoeabgJgjQX/s320/lock_screen.png" width="320" /></a></div>
<br />
2.- Set your account to do autologin<br />
Settings > User accounts > Automatic Login (remember to unlock the settings if you cant)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjq3BEhcsVfl02fKpjn0TAfZiDkuTp-hVs89LcL1u4Nzq8urYU-Yo32wkQSrm2ZBtJzFyuUaKaVjO4wQKz8AfX1U-CpO7gWoWs3PLVjR708GKHHBq3dR2aPUgNHQKcyZhYJWaUCNPlk3HDp/s1600/autologin.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="164" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjq3BEhcsVfl02fKpjn0TAfZiDkuTp-hVs89LcL1u4Nzq8urYU-Yo32wkQSrm2ZBtJzFyuUaKaVjO4wQKz8AfX1U-CpO7gWoWs3PLVjR708GKHHBq3dR2aPUgNHQKcyZhYJWaUCNPlk3HDp/s320/autologin.png" width="320" /></a></div>
Once it is changed, Lock again the settings.<br />
<br />
3.- Add your screen saver to be run on boot<br />
Startup Application Preferences > Add<br />
then add an entry for the screen saver:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2Bx5YgS5BEawTxsC6eIN8SvqxhY5UcEVvEg8BTXBWiXSMDJLndHPbOBplcSxuLWbST3QHFXTJH3tiabflKcJMcMI8TFvsCjQhju0sJGCc2S_IKtn4vjupPLs9-f57AT0uWfztdFUfFfpw/s1600/add_startup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="260" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2Bx5YgS5BEawTxsC6eIN8SvqxhY5UcEVvEg8BTXBWiXSMDJLndHPbOBplcSxuLWbST3QHFXTJH3tiabflKcJMcMI8TFvsCjQhju0sJGCc2S_IKtn4vjupPLs9-f57AT0uWfztdFUfFfpw/s320/add_startup.png" width="320" /></a></div>
<br />
Name: (whatever)<br />
Command: /usr/bin/gnome-screensaver-command -l<br />
Comment: (whatever)<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhq_0xo-FL-TKLxL7zZ3cOSBeF28nOGda8CfQlxer0mK7Subg1rHk9uZSm7QYZT_atIxNgGNwuFcXkWqCqAZOGIrkf4y88tXk8PqvaPfG9wsD2ONpZ0ijKUha_YzIGpFwbWLjsSzqDeGZiz/s1600/add_startup_entry.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="123" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhq_0xo-FL-TKLxL7zZ3cOSBeF28nOGda8CfQlxer0mK7Subg1rHk9uZSm7QYZT_atIxNgGNwuFcXkWqCqAZOGIrkf4y88tXk8PqvaPfG9wsD2ONpZ0ijKUha_YzIGpFwbWLjsSzqDeGZiz/s320/add_startup_entry.png" width="320" /></a></div>
<br />
Save.<br />
<br />
For the locking command, I used:<br />
<code>gnome-screensaver-command --lock</code><br />
<br />
or<br />
<br />
<code>xdg-screensaver lock</code><br />
<br />
<br />
4.- Bonus: I like to add my email program to the start up, so similarly <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF5MWHfsdgFaZtkpHk0sRxU0FLatONyUFA6MjVaGZSPCoaWBYN8eIwR-A4hyphenhyphen0Aqr-NQ21Wq0-uKR-JA7NmxJ30qBf7r4WVe4GbLN3x1ajXWlWls0hFeco6xez7DJU6_-73CFFIsZyV4and/s1600/thunderbird.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="170" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF5MWHfsdgFaZtkpHk0sRxU0FLatONyUFA6MjVaGZSPCoaWBYN8eIwR-A4hyphenhyphen0Aqr-NQ21Wq0-uKR-JA7NmxJ30qBf7r4WVe4GbLN3x1ajXWlWls0hFeco6xez7DJU6_-73CFFIsZyV4and/s320/thunderbird.png" width="320" /></a></div>
<br />
Done! Next time your machine boots, the screen will be locked with your screensaver.<br />
<br />
<br />
<br />gsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0tag:blogger.com,1999:blog-6931502956661392072.post-21982436916042465392012-11-17T17:26:00.005+01:002012-11-17T17:28:48.646+01:00Rails 3: Improve :presence validationIn Rails 3.0 (and probably in modern versions) you can ensure the validity of your models by using <a href="http://guides.rubyonrails.org/active_record_validations_callbacks.html" target="_blank">validations</a> . One I use is the :presence , to make an object invalid if a variable is not present.<br />
<br />
Example:<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; background: white; border-width: .1em .1em .1em .8em; border: solid gray; color: black; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: green; font-weight: bold;">class</span> <span style="color: #b00060; font-weight: bold;">Car</span> < <span style="color: #003060; font-weight: bold;">ActiveRecord</span><span style="color: #303030;">::</span><span style="color: #003060; font-weight: bold;">Base</span>
belongs_to <span style="color: #a06000;">:user</span>
validates <span style="color: #a06000;">:user</span>, <span style="color: #a06000;">:presence</span> => <span style="color: #003080; font-weight: bold;">true</span>
<span style="color: #303030;">.</span>.<span style="color: #303030;">.</span>
<span style="color: green; font-weight: bold;">end</span>
</pre>
</div>
<br />
The problem comes when you investigate how validation is made. Each time we save the validated object is got from the database (*).
Something simple as:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; background: white; border-width: .1em .1em .1em .8em; border: solid gray; color: black; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;">car = <span style="color: #aa0000;">Car</span>.last
car.save
</pre>
</div>
<br />
will hit the database checking if user exists.<br />
<br />
Solution: build a custom Validator and use it:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; background: white; border-width: .1em .1em .1em .8em; border: solid gray; color: black; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: green; font-weight: bold;">class</span> <span style="color: #b00060; font-weight: bold;">SexyForeignKeyValidator</span> < <span style="color: #003060; font-weight: bold;">ActiveModel</span><span style="color: #303030;">::</span><span style="color: #003060; font-weight: bold;">EachValidator</span>
<span style="color: grey;">#http://guides.rubyonrails.org/active_record_validations_callbacks.html#performing-custom-validations</span>
<span style="color: green; font-weight: bold;">def</span> <span style="color: #0060b0; font-weight: bold;">validate_each</span>(record, attribute, value)
<span style="color: green; font-weight: bold;">return</span> <span style="color: green; font-weight: bold;">if</span> value<span style="color: #303030;">.</span>present?
relation_name <span style="color: #303030;">=</span> attribute<span style="color: #303030;">.</span>to_s<span style="color: #303030;">.</span>gsub(<span style="background-color: #fff0ff; color: black;">/_id$/</span>,<span style="background-color: #fff0f0;">''</span>)
<span style="color: green; font-weight: bold;">unless</span> record<span style="color: #303030;">.</span>send(relation_name)
record<span style="color: #303030;">.</span>errors<span style="color: #303030;">.</span>add relation_name<span style="color: #303030;">.</span>to_sym, <span style="color: #a06000;">:blank</span>
<span style="color: green; font-weight: bold;">end</span>
<span style="color: green; font-weight: bold;">end</span>
<span style="color: green; font-weight: bold;">end</span>
</pre>
</div>
<br />
The tricky part is to check for the foreign_key BUT if this is not already set (on create), check for the relation.<br />
This validation run on the foreign key, so we modify the code in the model.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; background: white; border-width: .1em .1em .1em .8em; border: solid gray; color: black; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: green; font-weight: bold;">class</span> <span style="color: #b00060; font-weight: bold;">Car</span> < <span style="color: #003060; font-weight: bold;">ActiveRecord</span><span style="color: #303030;">::</span><span style="color: #003060; font-weight: bold;">Base</span>
belongs_to <span style="color: #a06000;">:user</span>
validates <span style="color: #a06000;">:user_id</span>, <span style="color: #a06000;">:sexy_foreign_key</span> <span style="color: #303030;">=></span> <span style="color: #003080; font-weight: bold;">true</span>
<span style="color: #303030;">.</span>.<span style="color: #303030;">.</span>
<span style="color: green; font-weight: bold;">end</span>
</pre>
</div>
<br />
One last note. The code for the validator must be included in some path that is automatically loaded ( <a href="http://stackoverflow.com/a/6845733/1265056" target="_blank">read this</a>) <br />
<br />
Profit!<br />
<br />
<br />
(*) Note that this is a very particular case, when we check the presence of a relation. Checking for the foreign_key user_id won't work, since on creation, this foreign_key can be nullgsusmonzonhttp://www.blogger.com/profile/01676733640860072666noreply@blogger.com0