Wednesday, October 6, 2021

Install mysql with docker in your local machine (ubuntu 20.04)

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.

These are the two guides I followed for reference : 

 Preparation

In my case docker was already installed. docker can run from my user, so no need to append `sudo` to any `docker` command

Install mysql server via docker

The key here is to use the official mysql package from docker hub

# https://hub.docker.com/_/mysql
docker image ls
docker pull mysql:latest
docker image ls

# create a volume in local machine
docker volume create mysql-data
docker volume ls
docker volume inspect mysql-data

# map a local directory to host the config
mkdir -p /home/jesus/projects/docker/mysql-volume/conf.d
echo -e '[mysqld]\nmax_connections=100\nmysqlx= 0' > /home/jesus/projects/docker/mysql-volume/conf.d/my-custom.cnf

then create the container

docker run \
--detach \
--name=mysql_server \
--publish 3306:3306 \
--volume=/home/jesus/projects/docker/mysql-volume/conf.d:/etc/mysql/conf.d \
--volume=mysql-data:/var/lib/mysql \
--env MYSQL_ROOT_PASSWORD=root \
--env MYSQL_ALLOW_EMPTY_PASSWORD=yes \
mysql:latest

docker ps
docker logs mysql_server

change root password

# access to the container in interactive mode
docker exec -it mysql_server bash


#supply initial root password (to enter mysql inside the container)
mysql -uroot -proot

# SET PASSWORD FOR 'root' = 'something-super-secret';
SET PASSWORD FOR 'root' = '';
FLUSH PRIVILEGES;

# enable docker access from anywhere (probably the image has this already)
UPDATE mysql.user SET host = '%' WHERE user='root';
FLUSH PRIVILEGES;

Install mysql client

Install the client in your machine that accesses the mysql server running in the docker container. For other distributions adjust the method to install the mysql client (only the client is needed). The mysql site provides that information.

First, add the mysql repo. Go to https://dev.mysql.com/downloads/repo/apt/ and get the latest repository available. In my case (@2021-10-01). Adjust the .deb package accordingly.

# add the repo
wget https://dev.mysql.com/get/mysql-apt-config_0.8.19-1_all.deb
#Click OK with the default options or select which mysql you want
sudo dpkg -i mysql-apt-config_0.8.19-1_all.deb
rm mysql-apt-config_0.8.19-1_all.deb

# install the client from the repo
sudo apt-get update
sudo apt-get install mysql-client
# sudo apt-get install mysql-server

ensure it works

mysql -uroot -h127.0.0.1 -P3306
# or with password
# mysql -uroot -p -h127.0.0.1 -P3306

😄 Fin!

P.S. Useful commands

docker start mysql_server
docker stop mysql_server
docker restart mysql_server
docker ps
docker volume inspect mysql-data
docker container ls --all

mysql -uroot -p -h127.0.0.1 -P3306

# dangerous!
docker rm mysql_server
docker volume rm mysql-data



Wednesday, October 23, 2019

Pattern to solve circular dependencies in node,

This is an useful pattern to avoid circular dependencies while requiring other files in node.

Circular dependencies manifest when a require returns an empty object: "the circular dependency means that other code receives an incomplete module object"

The solution is to 'augment' the exports instead of replacing it. 
So, if another module got a reference to the exported members they are eventually completed.

//requires at the beginning, as usual
//.. 


const PublicMethods = {
 method1: () => {},
 method2: (x) => {...},
 ...
]; 

// -- private & other methods
... 
...

//augment the exports at the end of the file:
Object.assign(module.exports, PublicMethods);



Wednesday, August 23, 2017

Get Response Time with Curl - curltime

Save this script in your path  (echo $PATH) as curltime:

#!/bin/bash

/usr/bin/curl -o /dev/null \
-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" \
$1


Make it executable

chmod 0755 /usr/local/bin/curltime


Now you can use it to check the response time of an url. Ex:

curltime https://marca.com

Source: based on https://viewsby.wordpress.com/2013/01/07/get-response-time-with-curl/

Wednesday, June 14, 2017

Merge 2 commits when there are commits in between

Super handy trick based on based on https://stackoverflow.com/questions/30704254/merge-two-commits-where-commits-are-between

Lets say we have 2 commit we want to merge

f68ffb5 commit 6
...
d294fac commit 1


This is important: you have to give reference to a commit before the first one involved (in this case, the one that you want to squash to). Here we make the "before" reference with ^.

# rebase to the commit **before** commit 1
git rebase -i d294fac^

You invoked an interactive rebase. An editor window opens. Rearrange commits as desired (read the notes in the editor that opens)

In this list the commits go in reverse order (not as in git log): from the earliest to the latest.

Rearrange lines in the following way:

pick d294fac     //--- commit 1
squash f68ffb5   //--- commit 6
pick       //--- commit 2
pick       //--- ommit 3
pick       //--- commit 4
pick       //--- commit 5


or (leave the squashed commits in last place)
pick d294fac     //--- commit 1
squash f68ffb5  
//--- commit 6
pick      
//--- commit 2
pick      
//--- ommit 3
pick      
//--- commit 4
pick      
//--- commit 5

Tuesday, September 6, 2016

Back In Time: Backups for your personal computer


'Back in Time' ( http://backintime.le-web.org/ ) is a linux program to make regular backups. I recommend it.

Why?

  • simple. Linux only.
  • it uses incremental backups and hardlinks, saving time and space between 'snapshots'
  • '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: you can plug your backup drive to another computer and access the snapshots from the file explorer. No need to install anything to read the backup
  • autoremove old backups based on age or free space
  • backups are made to a drive or a remove machine (ssh). No cloud storage option (S3, box, etc)
  • handles backups of several machines with no extra configuration. Snapshots of different machines are stored in different folders
  • Just works


How I use it

(for all my computers) I run Back in time backups on demand (not scheduled) and unencrypted. Both options are available if needed.
Backups are stored in a ext4 external drive (ext4 is linux native but still read-accessible from OSX and Windows)


Install

This works for ubuntu. For others, go to the original http://backintime.le-web.org/

sudo add-apt-repository ppa:bit-team/stable
sudo apt-get update
sudo apt-get install backintime-gnome
Note: latest versions are only qt-based. You could install the `backintime-qt4` package instead `backintime-gnome`

Configure

Connect the external drive for the backups, and launch Back in Time. The first time you need to configure how to make backups for the computer (accessible from settings / preferences as well).

 

In the settings, you need to set where and when to save snapshots. For example I choose

[General] tab
  • Profile: Main profile
  • Mode: local (or local encrypted)
  • Where to save snapshots / destination: your drive , partition or subfolder.
  • Schedule disabled. I will save a new snapshot on demand
[Include] , [Exclude] tab
I usually include the home folder, or just the Documents, Pictures and .ssh folder. I exclude the Music and Videos folder.

[Autoremove]
defaults are good for me

[Options] 
  • Check Use checksum to detect changes
  • Check Full rsync mode (or not)
  • Check Backup replaced files on restore
  • Uncheck 'continue on errors'


[Expert options]

  • Uncheck any option related to cron
  • Check ‘Run ionice’ when taking a manual snapshot
  • you may check 'Preserve ACL' ( as src and dst are Ext4, otherwise uncheck ). I finally uncheck it since gave me errors for some of my files


-


Save configuration > OK

New backup

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.





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 Settings > General , in the Advanced section

sudo -i backintime-qt4 --config ~/.config/backintime/config --profile-id 1

Uninstall

sudo ppa-purge ppa:bit-team/stable
sudo apt-get remove backintime-*

Saturday, September 3, 2016

Nautilus: Add touch action in context menu

This is a modification I make to nautilus in every computer I own.

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.



First, install nautilus actions

sudo apt-get install nautilus-actions

and launch it.

nautilus-actions

(1) Create a new item
  • Create the item using the '+' icon and name the action (Ex: 'Touch'). Use a short name, since it is used in teh context menu
  • Edit Action and Command tabs accordingliy. Ex:
    • Path: /usr/bin/touch
    • Parameters: %F (see Legend button to get more options)
    • Working directory: %d
  • Save (icon save)




(2) Right click on the new action that appeared in the Item list area
  • go to preferences
  • Ensure the 'Nautilus menu layout' > 'Create a root...' is disabled

Restart Nautilus
pkill nautilus


Well done!

Sunday, July 19, 2015

How to determine the time zone of an IP in Ruby

In ruby / Rails, you may need to get the timezone of an ip address.

One of the best options is the Maxmind.com GeoIP . The solution provided here applies to geolocate an IP address to get the timezone, country, city etc

Local or Remote

For better results I recommend to use their web service to geolocate a city : http://dev.maxmind.com/geoip/geoip2/web-services .
The service is not free but you can pay 20$ for 50K resolutions. Fair enough.
I write a new post on how to query the web service.

Another option is to resolve the geolocation locally. Maxmind provides a free database 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/

Local set up

We need:
- download the database
- include the `geoip` gem, that will read the database
- our code to query the geoip gem
- link back to Maxmind

Download database

Read http://dev.maxmind.com/geoip/legacy/install/city/ for instructions. It boils down to:

cd /tmp
wget -N http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
gunzip GeoLiteCity.dat.gz
mv GeoLiteCity.dat /usr/local/share/GeoIP/

I recommend you to make a bash script with these commands and run a cron once a month

Required gem


To read the legacy GeoIP database I recommend the pure ruby geoip gem. Just include in your Gemfile
 gem 'geoip'

or
 gem install geoip

Boilerplate

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.
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


class Ip2Geo

  DB_PATH = '/usr/local/share/GeoIP/GeoLiteCity.dat'

  def timezone(ipv4)
    if ipv4.present?
      addr = IPAddr.new(ipv4.to_s.strip)
      32.downto(16).map do |mask| #if an address fails, test with enclosing network X.X.X.X , X.X.X.0 , X.X.0.0 ...
        network = addr.mask(mask).to_i
        c = db.city(network)
        if c && c.timezone
          return c.timezone
        end
      end
    end
    nil
  end
#a similar pattern can be re-used
  def country_code(ipv4)
    if ipv4.present?
      candidate = ipv4.to_s.strip
      addr = IPAddr.new(ipv4.to_s.strip)
      32.downto(16).map do |mask|
        network = addr.mask(mask).to_i
        c = db.city(network)
        if c && c.country_code2
          return c.country_code2
        end
      end
    end
    nil
  end

  def location(ipv4)
    if ipv4.present?
      candidate = ipv4.to_s.strip
      addr = IPAddr.new(ipv4.to_s.strip)
      32.downto(16).map do |mask|
        network = addr.mask(mask).to_i
        c = db.city(network)
        return c if c
      end
    end
    nil
  end


  def db
    @db ||= GeoIP.new(DB_PATH)
  end

end

To resolve the ip

locator = Ip2Geo.new
locator.timezone('193.110.128.199')

Attribution

Remember that the use of the free database requires an attribution: The attribution requirement may be met by including the following in all advertising and documentation mentioning features of or use of this database 
This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com



Friday, June 12, 2015

Canon lide 220 in Ubuntu 14.04

Last week I bought a Canon lide 220 scanner. You get a decent device for the price.

(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).

connect the scanner

ensure it is recognized

    lsusb

in teh output, search for the Canon device

...
Bus 003 Device 003: ID 04a9:190f Canon, Inc.
...

ensure your user has access to the bus (otherwise  you'll have to launch everytime  xsane as root)

    sudo chmod u+w /dev/bus/usb//

in my case

 sudo chmod u+w /dev/bus/usb/003/003

Then, add this ppa to install latest version of xsane

sudo add-apt-repository ppa:rolfbensch/sane-git

and install xsane

    sudo apt-get update && sudo apt-get install gocr libsane-extras xsane-common xsane

if you have problems, install a couple of additional packets with

    sudo apt-get update && sudo apt-get install gocr libusb-dev libsane-dev libsane-extras xsane-common xsane

now you can run

  xsane


 and the scanner is working!

Thursday, December 4, 2014

Quicktip : Remove invalid utf8 charactes in a file

Whenever I need to check if a file contains invalid utf8 chars:

isutf8 file.txt

(in ubuntu, you need to install the `moreutils` package)

Then, to rremove invalid chars, use iconv:

iconv -f utf-8 -t utf-8 -c nonutf-8.txt > utf8.txt

-c stands for remove `invalid chars`
-f 'from' utf8
-t 'to' utf8 

Wednesday, October 15, 2014

Quick Tip: nginx.conf file for upstart in Ubuntu 14.04

It tookme several tries to find the right configuration file for nginx to work with upstart (for example Ubunty 14.04 uses upstart)

The configuration file is based in the oficial nginx file, but modified to allow restarts (*)


# nginx
 
description "nginx http daemon"
author "George Shammas "
 
start on (filesystem and net-device-up IFACE=lo)
stop on runlevel [!2345]
 
env DAEMON=/usr/sbin/nginx
env PID=/var/run/nginx.pid
 
expect fork
respawn
respawn limit 10 5
 
pre-start script
        $DAEMON -t
        if [ $? -ne 0 ]
                then exit $?
        fi
end script
 
# Ensure nginx is shutdown gracefully
# Upstart will be tracking the wrong PID so the following is needed to stop nginx
post-stop exec start-stop-daemon --stop --pidfile $PID --name nginx --exec $DAEMON --signal QUIT

exec $DAEMON -c /etc/nginx/conf/nginx.conf
--- (*) Without the post-stop script, nginx didnt kill the forked processed and spit a 502 Bad Gateway . #you can see taht children are not killed after a `service nginx stop` sudo netstat -tulpn #to kill stalled children sudo fuser -k 80/tcp

Wednesday, May 21, 2014

Quick tip: CSS for Hanging indentation


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':

This is the first line of a very very very very very
    very very very very very very very very very very
    long paragraph. And there is more than one sentence.

    In fact we could reach almost a dozen.


Just apply a combination of a negative text-indent and padding-left (or margin-left). Ex:

p {
  text-align: left;
  text-indent: -25px;
  padding-left: 25px;
}


There is a 'first-letter' property in css that gives you more customization @see this

Monday, May 19, 2014

Quick tip: optimize images from command line

I use this trick for quickly optimize images. Note, I use ubuntu:

just install these tools

sudo apt-get install optipng jpegoptim

and now you can do (beware: input is overwritten)

optipng file.png 
jpegoptim file.jpg

both tools work in the same way

jpegoptim [ options ] filenames
optipng [options] files



to convert between formats, you can use imageMagick

sudo apt-get install imagemagick

and now

convert input.png output.jpg
convert input.jpg output.png 

Profit!

Sunday, March 23, 2014

Handy enhancement for ruby String: Defaults to default text

In my code , I repeat a lot this pattern: if the string is blank / empty, return a default text, otherwise return the original text.

final_text = text.blank? ? "default text" : text content_tag :h1, text.blank? ? "default text" : text, :class => 'mega'


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.

class String

  def defaults_to(what)
    self.strip!
    self.blank? ? what : self
  end


end

and, now you can use it in a readable form

text.defaults_to("default text")


Saturday, March 1, 2014

Concerns in Rails (or how to reuse code)

I have been doing some experiments with rails Concerns . While they have been widely publicited for Rails 4, you can use them in Rails 3 as well.

They are like the ruby standard include / extend pattern, but taking care of dependencies. In any case it would be easy to go back to the 'raw' include / exclude game.

This is an example of how to use them.

 require 'active_support/concern'

module Concerns

  module DummyConstants

  end

  module Dummy
    extend ActiveSupport::Concern

    #it seems that things declared here are shared between all inclusions (saving memeory)
    SEXY_REGEXP = /SEXY_REGEXP/i
    MEM_HOGGING = Array.new(1024 * 1024)
    MEMBERSHIP_STATUSES = %w(accepted invited requested rejected_by_group rejected_group)

    included do
      #The included block will be triggered at inclusion time
      before_create :stuff_on_creation

      attr_accessor :accesor_for_the_instance
      class << self
        attr_accessor :accesor_for_the_class
      end

    end

    module ClassMethods
      #Methods in ClassMethods will get added class methods
      def dummy?
        puts "\nCLASS dummy? called\n"
        true
      end

    end

    #these methods are added as instance methods

    def dummy?
      puts "\nINSTANCE dummy? called\n"
      true
    end

    def stuff_on_creation
      puts "\ncreation called\n"
    end

  end


end


Just create a file under model/concerns/dummy.rb and include it

User.send :include, Concerns::Dummy

or

class User
 include Concerns::Dummy end

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 using some trick.

I find easier to understand if I include the 'Concern', tough.

Profit!




Saturday, February 8, 2014

Descubriendo los podcasts

Un 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.

--

Todo se inicia con un tweet de @david_bonilla 
"Yo salgo a correr para tener una excusa para escuchar el ". Así descubro que "El amuleto de yendor" es un podcast sobre tecnología, dode 2 chavales hablan sobre cosas que me interesan.


El prime paso, fue descargarme el último programa desde ivoox : 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).

--

Tras un mes de aprendizaje, así es como gestiono mis podcasts:

gPodder.net es el servicio que me permite guardar mis subscripciones . Es gratuito. Necesitas crear  una cuenta y comenzar a añadir los podcasts que te interesen.

Lo siguiente es utilizar un programa que te descarge nuevos audios (episodios) según van apareciendo. Así te ahorras mucha gestion manual.
- para ordenadores tenemos clientes oficiales de gPodder en todas las plataformas http://gpodder.org/downloads así tengo mis WIndows y Linux sincronizados.
- para android yo utilizo ListeUp (no se cual es la diferencia entre la free o la pro 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.
- para el iPad utilizo la aplicacion de podcasts oficial . 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.
- Creo que VLC tambien puede manejar subscripciones de podcasts, pero no está integrado con gpodder.
- si no te interesa la integracion con gpodder, en android tienes PodcastAddict, que es una pasada
- si viajas mucho en coche, puedes pasar los episodios a tu reproductor de mp3 y escucharlo en el coche.

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.

--

Qué escucho?

Basicamente tecnología: iCharlas, PassionGeek, Infoxicados y el amuleto de Yendor.

Aqui estan mis subscripciones: en rss , opml o directamente la página de gpodder

Suelo descubrir nuevos podcasts, navegando por las subscripciones de otros usuarios que estan suscritos a mis mismos podcasts: algunas veces te llevas una sorpresa!

Dejo un enlace con uno de los episodios que mas me gustaron: Javifrechi (de infoxicados) en iCharlas, parte1 y parte2




Wednesday, November 13, 2013

2 lines that must be on your <head>

In few words:

ensure that these lines are included in your <head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>

Thursday, September 5, 2013

Take a photo from Javascript

Finally it is 2013 and you can take photos from your browser without requiring flash.

The API that makes it possible is getUserMedia ( http://caniuse.com/#search=getUserMedia ) and it is available in all modern browsers (requiring some vendor-prefixes, though).

For demo: use this fiddle

The code I paste here, takes a photo, dump it to a canvas and tries to upload it to a (non-existent) server.
- 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.
- Neither the video or the output are required to be shown, however is good to give visual feedback to users.
- NOTE: Chrome does not allow local files to get access to getUserMedia. You can use fiddle to make the test yourself.
- At the present, the api still needs to be prefixed depending on the browsers:
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;



<!DOCTYPE html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <title>getUserApi</title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width">
        <style type="text/css">
        video {
          background: rgba(255,255,255,0.5);
          border: 1px solid #ccc;
        }
        </style>
    </head>
    <body>
        <div id='text'>
            <p>You must grant access to the Camera first.</p>
            <p>Prompt would be shown above this lines, next to the address bar.</p>
            <button type='button' id='button'>Take photo</button>
        </div>
        <video id='video' width="640" height="480"></video>
        <canvas id='canvas' style="display:none;"></canvas>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.js"></script>
        <script>
        (function(window) {

          var nav = window.navigator,
            doc = window.document,
            //some browsers behave differently
            is_webkit = nav.webkitGetUserMedia,
            is_mozilla = nav.mozGetUserMedia,
            showSnapshot = true,
            showVideo = true,
            OUTPUT_RATIO = 0.5, //the output is X times the captured image (ex: we upload small photos)

            source,
            video,
            canvas,
            button,
            ctx,
            localMediaStream;

          var
              initCamera = function() {
              video = document.getElementById('video'),
              canvas = document.getElementById('canvas'),
              button = document.getElementById('button'),
              ctx = canvas.getContext('2d');

              //make canvas and video the same dimensions
              canvas.width = video.width * OUTPUT_RATIO | 0;
              canvas.height = video.height * OUTPUT_RATIO | 0;

              //turn canvas to visible
              canvas.style.display = showSnapshot ? '' : 'none';
              video.style.display = showVideo ? '' : 'none';
              (button || video).addEventListener('click', takeSnapshot, false); //addEventListener: IE9+ Opera7+ Safari, FFox, Chrome

              // if (is_webkit){
              //   nav.getUserMedia('video', onSuccess, onError);
              // }else{
              nav.getUserMedia({
                video: true
              }, onSuccess, onError);
              // }

            },
            onError = function(e) {
              alert('Camera permission rejected!', e);
            },
            onSuccess = function(stream) {
                if (is_mozilla) {
                  source = window.URL.createObjectURL(stream);
                } else if (is_webkit) {
                  source = window.webkitURL.createObjectURL(stream);
                } else {
                  source = stream;
                }

                video.src = source;
                video.play();
                localMediaStream = stream;
            }, stopCamera = function(){
              localMediaStream.stop();
              video.style.display =  canvas.style.display = 'none';
              localMediaStream = canvas = ctx = null;
              button
            }, takeSnapshot = function() {
            if (localMediaStream) {
              ctx.drawImage(video, 0, 0, (video.width * OUTPUT_RATIO) | 0, (video.height * OUTPUT_RATIO) | 0);
              uploadSnapshot();
            }
          }, uploadSnapshot = function(){
              var dataUrl;

            try {
                dataUrl = canvas.toDataURL('image/jpeg', 1).split(',')[1];
            } catch(e) {
                dataUrl = canvas.toDataURL().split(',')[1];
            }
            $.ajax({
                url: "localhost:3000/uploadTest",
                type: "POST",
                data: {imagedata : dataUrl}, //in the server file.write(Base64.decode64(imagedata)) , https://gist.github.com/pierrevalade/397615
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function () {
                    alert('Image Uploaded!!');
                },
                error: function () {
                    alert("There was some error while uploading Image");
                }
            });
          };


            //some browsers use prefixes
          nav.getUserMedia = nav.getUserMedia || nav.webkitGetUserMedia || nav.mozGetUserMedia || nav.msGetUserMedia;

          if (nav.getUserMedia) {
            initCamera();
          } else {
            alert("Your browser does not support getUserMedia()")
          }
        }(this));
        </script>
    </body>
</html> 

Bonus

For large images you can save some bytes by sending the image as a blob instead of a base64 encoding.

First, encode the dataUrl as a blob

function dataURItoBlob(dataURI, dataTYPE) {
  var binary = atob(dataURI), array = [];
  for(var i = 0; i < binary.length; i++) array.push(binary.charCodeAt(i));
  return new Blob([new Uint8Array(array)], {type: dataTYPE});
}

Then, you have 2 options:

- using the FormData api

function uploadWithFormData(dataUrl){
  // Get our file
  var file = dataURItoBlob(dataUrl, 'image/jpeg'),
  fd = new FormData();
  // Append our Canvas image file to the form data
  fd.append("imageNameHere", file);
  // And send it
  $.ajax({
     url: "/server",
     type: "POST",
     data: fd,
     processData: false,
     contentType: false,
  });
}

- or using the XHR

function uploadWithXHR(dataUrl) {
  var file = dataURItoBlob(dataUrl, 'image/jpeg'),
  xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  //add the headers you need
  // xhr.setRequestHeader("Cache-Control", "no-cache");
  // xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  // xhr.setRequestHeader("X-File-Name", file.name || file.fileName || 'image.jpg');
  // xhr.setRequestHeader("X-File-Size", file.size || file.fileSize);
  // xhr.setRequestHeader("X-File-Type", file.type);
  // xhr.setRequestHeader("Content-Type", options.type);
  // xhr.setRequestHeader("Accept","application/json, text/javascript, */*; q=0.01");
  xhr.send(file);
}

Friday, August 23, 2013

rubygems: uninstall some gems

I just discovored a way to uninstall only the gems that match a pattern (via https://coderwall.com/p/lpqmjq )

gem list [OPTIONAL PATTERN] --no-version | xargs gem uninstall -ax

for example

gem list hobo --no-version | xargs gem uninstall -ax

removes all 'hobo'

Successfully uninstalled hobo_jquery_ui-2.0.1
Successfully uninstalled hobo_clean_admin-2.0.1
Successfully uninstalled hobo_clean-2.0.1
Successfully uninstalled hobo_bootstrap_ui-2.0.1
Successfully uninstalled hobo_bootstrap-2.0.1
Successfully uninstalled hobo_jquery-2.0.1
Successfully uninstalled hobo_rapid-2.0.1
Removing hobo
Successfully uninstalled hobo-2.0.1
Removing hobofields
Successfully uninstalled hobo_fields-2.0.1

Sunday, August 11, 2013

JS: detect unsaved changes in a form

Here is a small jQuery plugin to detect changes on a form.

https://github.com/gsusmonzon/jquery.simple.unsaved

The tricky part is to store a hash of the serialization string instead of the full serialized form. The rest is not worth mentioning.

Friday, July 26, 2013

Solving ActiveRecord::ReadOnlyRecord in Rails 3


When you pass an SQL fragment to a finder, join or named scope, ActiveRecord returns read-only results by default.

Use readonly(false) in your queries to force that results are writable. Ex:

(Rails 3)
User.joins("INNER JOIN `cars` ON `cars`.`user_id` = `users`.`id` AND `cars`.`colour`  = 'electric blue'").readonly(false)