Tuesday, November 11, 2014

Reading MP3/OGG/M4A tags with pure JavaScript

Summary

There aren't many sources in the internet that show you how to read tags (metadata) from music files such as MP3, OGG or even M4A. Not only this, but they do use an external library. I've build an HTML5 player inspired by Foobar2000, called Noisy, and I needed it to be as lightweight as possible, so everything inside I wanted to do by myself and optimize it in time. Unfortunately, this required a lot of specifications reading and many retries to get the things working. Here I'll show you how I did the reading and will try explaining the meaning of the code (by comments). It's far from perfect, but there wasn't much in the internet to help me with that task, so if you have any suggestions you are more than welcome to share them with me. Without further ado, lets begin!

Prequel

All tags we read by getting the file as ArrayBuffer using HTML5 FileReader, so here is a sample of how to do that:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function onFilesSupplied( e )
{
 // Get all the files from the input of type file
 var files = e.files || e.dataTransfer.files,
  file,
  fr = new FileReader();

 // Handle the typed array read by the FileReader,
 // when it's finished loading
 function _handleFileLoad()
 {
  // This is the point where the real tag
  // read is happening - covered later on
  var tags =readTags( this.result );
 }

 // Listen when FileReader finishes reading and
 // pass result to the handler
 fr.addEventListener( 'load', _handleFileLoad );

 // This example uses only the first file to load,
 // but you can loop them all and read them
 file = files[0];

 // Do the actual read, by saying to the reader
 // we want the result as ArrayBuffer
 fr.readAsArrayBuffer( file );
}

This code is made to run on both input of type file or drag and drop events. Noisy is cloud player, so it rarely reads the files from disk, meaning that code is not used a lot. What Noisy uses instead is to make the cloud service return the file directly as ArrayBuffer. So a simple:


1
xhr.responseType = 'arraybuffer';

to the request does the trick, thus no FileReader is required. Unfortunately this works for Dropbox, but not for Google Drive (update: now it works!). That's the reason Noisy doesn't support tag reading when user uses Google Drive. If he uses Mozilla Firefox, though, Noisy will read the tags using audio.mozGetMetadata(), doesn't matter from where the file is coming. All that said, it really doesn't matter how you get your hands on the ArrayBuffer - what really matters is how we read the tags. I've split them in separate code snippets.

MP3 ID3v2


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
readTags( buffer )
{
 // We need only these tags for now. You can check specifications for other ones, if you need them
 toGet = {
  'TPE1': 'artist',
  'TIT2': 'title',
  'TALB': 'album',
  'TYER': 'date'
 };
 len = Object.keys( toGet ).length;
 i = 0;

 // Loop first 1000 bytes. Here I couldn't find better solution to where the tag entries end, so I just loop the first 1KB of data
 while( i < 1000 && len )
 {
  charCode = dv.getUint8( i++ );

  // Loop through all the characters in the configuration object on top and compare letter by letter with current one
  for( tag in toGet )
  {
   tagCode = tag.charCodeAt( 0 );

   // Check for lower case match
   if( charCode == tagCode )
   {
    match = true;
    // loop through all letters to make sure we have found a match
    for( k = 1; k < tag.length; k++ )
    {
     matchCharCode = dv.getUint8( ( i - 1 ) + k );
     tagCode = tag.charCodeAt( k );
     if( matchCharCode != tagCode )
     {
      match = false;
      break;
     }
    }

    // If a match is found get the tag
    if( match )
    {
     // Pattern for tag: TALB 00 00 00 (HEX for length of tag in bytes, grouped
     // by 2 for each char, the second being the first part in unicode, eg. 0415
     // for cyrillic ? is written 15 04 here) 00 00 (unicode flag byte. If 00 then
     // no unicode, else it's the first part of unicode char) (tag itself). All this
     // I found out by myself. Don't know if it's always true
     tagLength = i + 9 + dv.getUint8( i + 6 );
     tagValue.length = 0;
     // Check for *ÿþ symbols and read after them if found. Some tags have this funky
     // charachers upfront and I don't know why and what they mean
     i = ( 255 == dv.getUint8( i + 10 ) && 254 == dv.getUint8( i + 11 ) ) ? i + 12 : i + 9;

     // First byte shows if the tag is encoded in Unicode or not
     matchCharCode = dv.getUint8( i++ );

     // If unicode
     if( matchCharCode )
     {
      nextMatch = ( '00' + dv.getUint8( i - 1 ).toString( 16 ) ).slice( -2 );
      while( i <= tagLength )
      {
       matchCharCode = ( '00' + dv.getUint8( i ).toString( 16 ) ).slice( -2 );
       tagValue.push( '0x'.concat( matchCharCode, nextMatch ) );
       nextMatch = ( '00' + dv.getUint8( i + 1 ).toString( 16 ) ).slice( -2 );
       i += 2;
      }
     }
     else
     {
      i++;
      matchCharCode = ( '00' + dv.getUint8( i - 1 ).toString( 16 ) ).slice( -2 );
      while( i <= tagLength )
      {
       tagValue.push( '0x00' + matchCharCode );
       matchCharCode = ( '00' + dv.getUint8( i++ ).toString( 16 ) ).slice( -2 );
      }
     }

     // Substract last step increase, as next tag comes right after this one
     i--;

     // Save tag's value as we are done reading it
     metadata[toGet[tag]] = String.fromCharCode.apply( null, tagValue );

     // Remove tag name from the object, so we don't loop it again
     delete toGet[tag];
     len = Object.keys( toGet ).length;
    }
   }
  }
 }
}

I do not do ID3v1, but it's very easily implementable if you understand this code. I just decided it's too old to be supported - all modern MP3 files should have ID3v2.

OGG


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
readTags( buffer )
{
 // We need only these tags for now. You can check specifications for other ones, if you need them
 toGet = [
  'artist',
  'title',
  'album',
  'date'
 ];
 len = toGet.length;
 i = 0;

 // Loop first 1000 bytes. Here I couldn't find better solution to where the tag entries end, so I just loop the first 1KB of data
 while( i < 1000 && len )
 {
  charCode = dv.getUint8( i++ );

  for( j = len; j--; )
  {
   tag = toGet[j];
   tagCode = tag.charCodeAt( 0 );

   // Check for lower case match
   if( charCode == tagCode )
   {
    match = true;
    // loop through all letters to make sure we have found a match
    for( k = 1; k < tag.length; k++ )
    {
     matchCharCode = dv.getUint8( ( i - 1 ) + k );
     tagCode = tag.charCodeAt( k );
     if( matchCharCode != tagCode )
     {
      match = false;
      break;
     }
    }

    // If a match is found get the tag
    if( match )
    {
     // Byte before the 00 00 00 shows how many bytes the tag will be, including the "artist=" part,
     // so we read everything from "=" sign till the length is reached. All this
     // I found out by myself. Don't know if it's always true
     tagLength = i - 1 + dv.getUint8( i - 5 );
     tagValue.length = 0;
     i = i + tag.length;
     matchCharCode = dv.getUint8( i++ );
     while( i <= tagLength )
     {
      tagValue.push( matchCharCode );
      matchCharCode = dv.getUint8( i++ );
     }
     str = '';

     for( k = 0; k < tagValue.length; k++ )
     {
      str += '%' + ( '0' + tagValue[k].toString( 16 ) ).slice( -2 );
     }

     // Save tag's value as we are done reading it
     metadata[tag] = decodeURIComponent( str );

     // Remove tag name from the object, so we don't loop it again
     toGet.splice( j, 1 );
     len = toGet.length;
    }
    continue;
   }

   tagCode = tag.toUpperCase().charCodeAt( 0 );

   // Check for uppercase match
   if( charCode == tagCode )
   {
    match = true;
    tag = tag.toUpperCase();
    // loop through all letters to make sure we have found a match
    for( k = 1; k < tag.length; k++ )
    {
     matchCharCode = dv.getUint8( ( i - 1 ) + k );
     tagCode = tag.charCodeAt( k );
     if( matchCharCode != tagCode )
     {
      match = false;
      break;
     }
    }

    // If a match is found get the tag
    if( match )
    {
     // Byte before the 00 00 00 shows how many bytes the tag will be, including the "artist=" part,
     //so we read everything from "=" sign till the length is reachedl
     tagLength = i - 1 + dv.getUint8( i - 5 );
     tagValue.length = 0;
     i = i + tag.length;
     matchCharCode = dv.getUint8( i++ );
     while( i <= tagLength )
     {
      tagValue.push( matchCharCode );
      matchCharCode = dv.getUint8( i++ );
     }
     str = '';

     for( k = 0; k < tagValue.length; k++ )
     {
      str += '%' + ( '0' + tagValue[k].toString( 16 ) ).slice( -2 );
     }

     // Save tag's value as we are done reading it
     metadata[tag.toLowerCase()] = decodeURIComponent( str );

     // Remove tag name from the object, so we don't loop it again
     toGet.splice( j, 1 );
     len = toGet.length;
    }
   }
  }
 }
}

M4A


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
readTags( buffer )
{
 // We need only these tags for now. You can check specifications for other ones, if you need them
 toGet = {
  '©art': 'artist',
  '©nam': 'title',
  '©alb': 'album',
  '©day': 'date'
 };
 len = Object.keys( toGet ).length;
 i = 50000;

 // Loop second 50000 bytes. Here I couldn't find better solution to where 
 // the tag entries end, so I just loop the characters between 50KB and 100KB
 while( i < 100000 && len )
 {
  charCode = dv.getUint8( i++ );

  for( tag in toGet )
  {
   tagCode = tag.charCodeAt( 0 );

   // Check for lower case match
   if( charCode == tagCode )
   {
    match = true;
    // loop through all letters to make sure we have found a match
    for( k = 1; k < tag.length; k++ )
    {
     matchCharCode = dv.getUint8( ( i - 1 ) + k );
     tagCode = tag.charCodeAt( k );
     if( matchCharCode != tagCode )
     {
      match = false;
      break;
     }
    }

    // If a match is found get the tag
    if( match )
    {
     // Pattern: @nam 00 00 00 (byte showing length of tag, starting from next
     // byte) data 00 00 00 (byte showing I don't know what) 00 00 00 00 (text
     // for tag) 00 00 00 (byte showing I don't know what)
     i += 6;
     matchCharCode = dv.getUint8( i );
     tagLength = i + matchCharCode;
     i += 13;
     matchCharCode = dv.getUint8( i );

     tagValue.length = 0;

     matchCharCode = dv.getUint8( i++ );
     while( i <= tagLength && matchCharCode )
     {
      tagValue.push( matchCharCode );
      matchCharCode = dv.getUint8( i++ );
     }
     str = '';

     for( k = 0; k < tagValue.length; k++ )
     {
      str += '%' + ( '0' + tagValue[k].toString( 16 ) ).slice( -2 );
     }

     // Save tag's value as we are done reading it
     metadata[toGet[tag]] = decodeURIComponent( str );

     // Remove tag name from the object, so we don't loop it again
     delete toGet[tag];
     len = Object.keys( toGet ).length;
     continue;
    }
   }

   tagCode = tag.toUpperCase().charCodeAt( 0 );

   // Check for lower case match
   if( charCode == tagCode )
   {
    match = true;
    tag = tag.toUpperCase();
    // loop through all letters to make sure we have found a match
    for( k = 1; k < tag.length; k++ )
    {
     matchCharCode = dv.getUint8( ( i - 1 ) + k );
     tagCode = tag.charCodeAt( k );
     if( matchCharCode != tagCode )
     {
      match = false;
      break;
     }
    }

    // If a match is found get the tag
    if( match )
    {
     // Pattern: @nam 00 00 00 (byte showing length of tag, starting from next
     // byte) data 00 00 00 (byte showing I don't know what) 00 00 00 00 (text
     // for tag) 00 00 00 (byte showing I don't know what)
     i += 6;
     matchCharCode = dv.getUint8( i );
     tagLength = i + matchCharCode;
     i += 13;
     matchCharCode = dv.getUint8( i );

     tagValue.length = 0;

     matchCharCode = dv.getUint8( i++ );
     while( i <= tagLength && matchCharCode )
     {
      tagValue.push( matchCharCode );
      matchCharCode = dv.getUint8( i++ );
     }
     str = '';

     for( k = 0; k < tagValue.length; k++ )
     {
      str += '%' + ( '0' + tagValue[k].toString( 16 ) ).slice( -2 );
     }

     tag = tag.toLowerCase();

     // Save tag's value as we are done reading it
     metadata[toGet[tag]] = decodeURIComponent( str );

     // Remove tag name from the object, so we don't loop it again
     delete toGet[tag];
     len = Object.keys( toGet ).length;
    }
   }
  }
 }
}

I'm sure this code can be optimized a lot, but I'm still keeping it as simple as it can be so it's clear how things work. When I understand all the little details, then I'll merge things and make the code more compact.

Final words

Don't know if this article can help you, but I hope it can add to the end result you are looking for. If you know something that I missed, please do say - I'll be very happy to any optimizations and suggestions.

Monday, August 19, 2013

Replace SSD with Compact Flash card on Acer Aspire One ZG5

Earlier this year (2013) my SSD on AOA110 died. I had to choose what to do next - sell the netbook or fix it. As both things are fairly easy to be done and I love this netbook (in fact I'm currently writing this article on it), I chose to fix it, test it for some time and write on my blog about the results. So here we go.

What you'll need:


  1. Aser Aspire One ZG5 with SSD. Model of the SSD doesn't matter. The only thing that matters is that it should be PATA SSD with ZIF connector.
  2. CF/ZIF Convertor Circuit Board with ZIF Cables
  3. A compact flash card of your choice.
  4. Double-sided tape.
  5. Knowledge of how to tear down your AOA. You can find a video here.
  6. An hour or two, depending on your skills.


End result:

There are two tricky parts here:


  1. The cable you need to use is NOT supplied with this adapter. It's called flipped cable. As far as I know, it's not supplied with any adapter. You have to make it yourself. There are two cables coming with this adapter, so you have 1 mistake margin. I've read a couple of ways to make this cable, but I had another idea. The end result is to flip the pins of the cable so from one of the sides they should face down and the other up. Unfortunately, I didn't took a picture of my cable because it's hard to fit the flipped cable inside the adapter and when I succeeded I didn't wanted to tear it down again. Here is how I did it, though - see that both ends of the cable are a bit thick? That's made because the cable there should make good contact with the board. Since we want to flip the cable there, it'll have double thickness in the end, so we should remove this thick peace of plastic. If you don't (as I did in my first try), you will probably brake some of the buses and you won't be able to plug the cable into the adapter. So remove it and flip the cable exactly where the pins end, so they should not be visible on one of the sides of the cable.
  2. Compact flash card should be marked as fixed drive, not as removable drive, if you want to install Windows XP (for 7/8 I think it's not a problem, but I never tested it). For Linux it doesn't matter. If your CF card is marked as a removable drive, you won't be able to see it in the BIOS of your AOA - that should not worry you - it's still visible and bootable from the boot menu (F12). I bought 16GB 400X Transcend, as I read on the Internet that 90% of the Transcends are marked as fixed drives, but mine was not :( For Sandisks there is an utility for flipping this bit and making fixed disk from removable disk and vice versa. It's called ATCFWCHG and can be downloaded from here. For other brands I don't have any information.


After you make your flip cable and get your CF card, the rest is simple:


  1. Tear down the netbook.
  2. Make space for the adapter (you can look how I did it at the picture below).
  3. Glue the adapter there with the double-sided tape.
  4. Put the netbook back together and test it. Don't forget the ferrite ring on the cable (get the old one from the SSD).


Here is how I fit my adapter inside the AOA.

CF life expectations

I used my Transcend for 3 months (everyday use) and I can feel it slowing down as I did with the original SSD. That's really bad news for me. But the good news is that this is MLC flash card. Sandisk are offering SLC flash cards - they are pricey, but their life should be more than twice the length. Also the larger the capacity of your CF card is, the longer it's lifetime will be. So with 32GB SLC you should start feeling the slowdown not in 3 months, but in at least an year if not more. And that's if you use you netbook as I used mine - 2 to 6 hours each day (more in the weekends). It's up to you ;)

Friday, December 21, 2012

Automated ClixGrid clicking


If you are reading this, you probably know what ClixSense is and it's game - ClixGrid. If you do not know - check it out. Here I'll show you how to automate the clicking on the game (ClixGrid) - there is no captcha here, so you can easily do that with a bit of programming. I made a script in my spare time, so you won't have to program.

Here is a video of how the script is working:



WARNING! USE AT YOUR OWN RISK! I'M NOT RESPONSIBLE IF YOU GET YOUR ACCOUNT SUSPENDED!

Current Version: 20161023 (Changelog below)

How it works:

I use Selenium tests to mimic human actions. All the actions are applied without using the cursor, so you can use your mouse wherever you want - the clicks will still happen on the right places. The script generates random numbers to target the box it'll click. If the box is clicked, the script generates another set of numbers. If not - it clicks it, waits for the ad to complete and closes the popup. Then if there are tries left, it generates numbers again. Selenium doesn't solve the focus problem, though. Using only this script, you will automate the clicking, but you won't be able to use your PC, as you'll loose focus of the advertisements window, which will stop the timer. To solve this, I used a virtual machine - it has it's own focus, so I can use my primary OS as usual :) Because Clixsense can change the elements on the page, the script can stop working. I've included update feature to the script - after all clicking is done, the script opens this blog post, checks for newer version and alerts the user if one is available. It's good to be always up to date, to ensure the right working.

What you'll need:


1. My script.
2. Mozilla Firefox.
3. Selenium IDE addon.
4. Selenium IDE Flow Control extension.
5. (optional, but recommended) VirtualBox or any other virtual machine software.

How to install:

1. (optional) Install VirtualBox.
2. (optional) Install you OS of choice.
3. Install Firefox.
4. Install Selenium IDE addon.
5. Install Selenium IDE Flow Control extension.

How to update:

Just copy the new script over the old one and reload it, if you have loaded it into Selenium.

How to use:

1. Open Firefox.
2. Go to GlixGrid's page.
3. Open Selenium IDE (Ctrl+Alt+S).
4. Open my script from File -> Open (or press Ctrl+O).
5. On ClixGrid's board click on any box, watch the advertisement and close the window. If Firefox blocks the popup, allow all popups from clixsense.com and then continue with the instructions.
6. After one successful click on the grid, the script is ready to be run. Click on one of the play buttons you see on Selenium IDE and the clicking begins :) Watch the first one, just to be sure it's working. Sometimes, on start, Selenium IDE steals the focus from the popup, which stops the counter and the ad never completes, so be sure you watch the first ad and then you can go somewhere till this is done. If you are running the script in a virtual machine, then you can use your host OS as usual, because the script is running in the guest OS, which have it's own focus, so the windows are always focused, no matter the focused window on the host OS ;) This way you get a complete automation without having to watch the ads!

Changelog:
21122012: initial release

22122012:
- Added pause of 1s before clicking on close link. Till now, the period was a random number between 0 and 2s, but when the number was too low, "Stay on this page" dialog appeared, which brakes the script. Now the number is between 1 and 3s, so the problem should be solved.
- Now after version update check, if there is no newer version, the script redirects you to the GlixGrid page, to see your results of the clicking.
- Added check for remaining clicks at the beginning of the script. That way if you run the script for the second time, it won't fail, instead it'll go straight to checking for newer version.
- Removed useless echo command.

20121224:
- Removed onbeforeunload event from the popup. The fix in the previous version helped, but did not solved the problem entirely. Removing onbeforeunload event prevents "Stay on this page" dialog from ever appearing. In general, the dialog appears when you try to close the popup before he successfully reported to the parent site about the completion of the ad. So if there is no dialog to prevent this, the ad won't be counted, which is bad, but the script won't be broken, so he'll run the ad again, till it's counted.
- Fixed version numbering.

20161023:
- Fixed the script as it was not running at all (the id of the Close link in the popup was changed).

Monday, December 3, 2012

Points2Shop & CashPirate Android app download (apk)


p2s_intro by glamurtv

Points2Shop

I have Rena3 tablet, which is a nice piece of Chinese hardware. Good tablet, but as any Chinese thingy it has a horrible support. The latest Android version is 2.3.5 beta, which is bad, as the tablet is dual core with 512MB RAM, more than enough for Android 4.x. Not only I cannot upgrade to latter version of Android, but most of the apps in Google Play are "not compatible" with this device. I've tried many ways to unlock more apps, but I couldn't do it. One of the apps I really needed was Points2Shop. I ended up installing it on my sister's HTC phone and copying it from there to my tablet. It works flawlessly. So if you are like me and you cannot get the app from the market, here is a download link for the apk itself. I'll try to keep it up to date. Have fun!

PS: Here is my referral link, if you are interested in helping me ;)

Version 2.0.1017 (uploaded April 19, 2016 from Nexus 7 2013)
Download link: Points2Shop_com.points2shop.apk

Version 1.1.6 (uploaded September 16, 2015 from Nexus 7 2013)
Download link: com.points2shop.apk

Version 180.4.1 (uploaded March 30, 2015 from Nexus 7 2013)
Download link: com.darksciencemedia.p2s.android.apk

Version: 78.1 (Legacy)
Download link: Points2Shop.apk

CashPirate


CashPirate is an alternative to Poinst2Shop, which in my opinion is MUCH better. They pay for mobile app installations and they pay well. Also, they do instant PayPal redeems for as low as $2.5! You can get that for 3 days in a not well known country like Bulgaria - who knows how fast you can cash out in the USA! And of course they have the typical referral programs (10% for first level, 5% for second), but they also give the guy who registers as a referral a 50% increase in the earnings for the first 1000 coins ($1), which means by the time you normally get $1, you'll have $1.5 if you register as a referral to someone. So if you want to give it a shot, here is my referral code (for 50% bonus): CBVTUG. The coolest part of the app is that they allow multiple accounts under the same IP, so you and everyone in your family can install and use it without any problems, as long as it's on different devices of course. And get that - they can all be referrals to each other! Payments are done pretty fast - mine came for less than a minute after I requested it. They also don't send you any emails, so don't worry about SPAM. Here is a download link to the apk, in case you cannot download it from Google Play (the first link in this paragraph):

Version: 3.2 (updated June 23, 2014 from Nexus 7)
Download link: com.ayet.cashpirate.apk

Useful software

Both Poinst2Shop and CashPirate want you to install software on your device to earn money. Soon you'll run out of resources on the device so it's good idea to cleanup a bit. There are a lot of programs on the market for uninstalling apps, but I think the best way is still to use the Settings -> Apps to uninstall them. The only problem with that is that there is no quick way to access directly that menu. You have to open the drawer, choose Settings, scroll down to Apps and then you can uninstall. Fortunately, there is an app, Manage Applications Shortcut, which is just a shortcut to the integrated app manager in Android. Hope that helps!

Thursday, November 22, 2012

How to gain 100-350MB more free space upon Windows installation



When installing Windows Vista or 7 on a new hard drive, the OS creates a 100MB partition right at the beginning of the drive. If you are installing Windows 8, then the size of that partition is 350MB. I have Acer Aspire One ZG5 with 8GB SSD, which is very small and I have to make custom Windows installation, that use less space. Nevertheless, every MB counts! So if you are like me or you just hate that small partition that the OS creates, let me show you how to trick the setup not to create it. It's really simple - create partitions manually, not through the installer. Here's how:

1. Boot your installation.
2. When the setup wants you to choose partition to install Windows to, hit Shift + F10. This should bring command prompt to you.
3. Type diskpart and press Enter. Diskpart is an utility coming with Windows, so you really shouldn't do anything special to get it. In case you make your own installation, make sure you leave diskpart utility on the setup disk.
4. Type list disk and remember the number of the disk you want to partition. In my case it's 0, so replace 0 with your number from now on.
5. Type select disk 0.
6. Type clean (this will erase all the info on the drive, if any).
7. Now if you want the whole drive to be one partition (as in my case), type create partition primary. If you want to have more partitions, type create partition primary size=n where n is the size of the partition you want in MB. The rest of the partitions you can create again here, or later with the GUI.
8. Type select partition 1.
9. Type format fs=ntfs quick.
10. Probably not needed, but just to be sure type active.
11. Type exit, to exit Diskpart and then exit again, to exit cmd.
12. From the setup choose the partition you've just created and proceed with the install without loosing 100/350MB :)

Wednesday, August 8, 2012

DBSyncer - A tool to synchronize databases through repository


Recently I started cleaning my PC from old, useless code and projects that I had been working on in the past and then I came across DBSyncer - a project intended to sync databases through repositories. Pretty useful if you and your colleagues (or friends) are developing a website or system, and you need a tool to sync the database between all the dev computers. I started developing this project in my spare time back in the beginning of 2011 because I needed such tool at work, where we were constantly  fighting with database synchronization through our subversion. But then in May 2011 I found a better job, where I didn't need this tool anymore, so development halted. Still, the made progress is pretty good - DBSyncer is fully working. I never released it because I wanted to implement a couple of features to it, but never got the time. Now, after more than an year, I'll release it as is - I'm sure there will be people happy to use it. I only replaced deprecated functions in newer PHP versions with working ones. So, what DBSyncer offers?

  • Automated database synchronization
  • Database change revisions
  • Database revert to revision
  • Well documented API
  • GUI

The main part of DBSyncer is it's PHP class - DBSyncer.php (only about 33kb). All other files are optional. If you choose to use only this file, you'll have to make the settings manually. Info about this, how to use DBsyncer and what methods it offers, is provided in the docs. If you want to use the GUI, then you'll need all files from the archive (available to download at project's page in Google Code). GUI can help you with the following things:

  • Install DBSync
  • Add new database
  • Import database
  • Remove database
  • See revision changes for each database
  • Apply revision to database both forward and backwards
  • Add or remove revisions
  • Export database without data (structure only)
  • Make changes to the config

All of these things are provided by DBSyncer.php. The GUI only helps you to manage databases visually. Also note GUI won't sync your databases automatically. If you want that, then you'll need to implement DBSyncer.php in your project and use its methods. See docs for more info.

Main screen
Screen showing database revisions

There is some unfinished work with the GUI - no "please wait" screen when applying revision (probably somewhere else too). If I have time, I'll fix this.

Final words - Although not entirely finnished, DBSyncer is still a good piece of work if it fits your needs. Enjoy!

Wednesday, July 4, 2012

EasyPeasy on Aser Aspire One


Yet another post of the series. This time I tried EasyPeasy on my AOA110. It works really good, with the exception of WiFi - no bundled driver inside the .iso image. Also brightness settings are not working - only fn key combinations can be used to control it. Nevertheless if you want to try it, you'll need:
1. EasyPeasy images.
2. UNetbootin.
3. At lease 1GB flash drive.
Now run UNetbootin and select EasyPeasy's image and write it to your flash drive. Boot the flash drive and have fun :)