React Native CodePush

CodePush is a magical service that lets you update your app on the fly! It means that when you find a bug in your app you can push the code instantly to your users and update their apps / release new features.

Why should I care?

With Native apps you have to go through app review process every time you release a new version. Sometimes its fast, sometimes its not. But with CodePush you can update JS part of the app bypassing the review!

Me wants!

Setup is actually quite easy and documentation pretty good. You  need to:

  1. Install CLI tools
  2. Register your app
  3. Install CodePush in your app
    npm install --save react-native-code-push
    react-native link react-native-code-push
    
  4. Wrap your top-level component in a codepush wrapper
    import codePush from "react-native-code-push";
    class TeamParrot extends Component {
    ...
    }
    export default codePush( TeamParrot );
    
  5. Add deployment keys to your project
    1. iOS: add to info.plist:
      
      CodePushDeploymentKey
      your key
  6. To update and rollback your app:
    code-push release-react TeamParrot-ios ios
    code-push promote TeamParrot-ios Staging Production
    code-push rollback TeamParrot-ios Production
    
  7. Profit

React Native Isomorphic app over the weekend

This article is part of my “React Native Isomorphic app over the weekend” series. It shares the problems I encountered during development of TeamParrot and Headstart Journal.

React Native: Rename my app

You started building your app before you knew what it was, didn’t you? Well, that is certainly what I did.

My app started as “SlackBird”, then I realized that i may be violating some trademark there ( hint: probably not “bird” ), so I went and changed the name to “TLDR“. But after that I realized my addiction to Party Parrot so “TeamParrot” it is!

So what files should I actually change?

IOS

  • When you go to project details in Xcode, you can edit name. When you edit it, Xcode “Rename files” tool opens.Screen Shot 2016-10-19 at 22.37.32 PM.png
  • In ios/PROJECT/info.plist change CFBundleURLName
  • In ios/PROJECT.xcodeproj/xcshareddata/xcschemes/PROJECT.xcschemechangeBuildableName`
  • Rename file ios/PROJECT.xcodeproj/xcshareddata/xcschemes/PROJECT.xcscheme itself to NEWPROJECT.xcsccheme

Android

I just grepped for files containing my old name “TLDR” in this instance. These variables needed to be changed:

  • package in android/app/BUCK
  • applicationId in android/app/build.gradle
  • package in android/app/src/main/AndroidManifest.xml
  • return value of getMainComponentName in android/app/src/main/java/com/slackbird/MainActivity.java . That is the name of your main component in JavaScript portion.
  • package in android/app/src/main/java/com/PROJECT/MainApplication.java
  • Application name in android/app/src/main/res/values/strings.xml

React part

Now you renamed your min entry point to your application. So don’t forget to rename your main component name:

AppRegistry.registerComponent( 'TeamParrot', () => Login );

If you renamed everything, some directories will still have old name of your app. I decided I’m fine with that.

React Native Isomorphic app over the weekend

This article is part of my “React Native Isomorphic app over the weekend” series. It shares the problems I encountered during development of TeamParrot and Headstart Journal.

React Native: Vector Icons – FontAwesome and others

You want some nice icons in your app, don’t you? In my laziness I tried using emojis instead, but it turned out to be sub-par.

So I conceded and installed an icon package called

React Native Vector Icons

npm install react-native-vector-icons --save to install

How this works

This package provides fonts and some wrappers. Since SVG is still not working 100% on RN, introducing vector icons via font seems like a reasonable approach. In mobile apps you want to bundle the font with the app itself.

IOS

Theoretically, you should be able to rnpm link. Unfortunately, that didn’t work for me, so I had to install font manually:

  • Drag “Fonts” folder from node_modules/react-native-vector-icons to your project
  • You have to add fonts in info.plist This is how I added FontAwesome only:

UIAppFonts : Fonts/FontAwesome.ttf

ANDROID

In android/app/build.gradle

project.ext.vectoricons = [
    iconFontNames: [ 'FontAwesome.ttf' ] // Name of the font files you want to copy
]

apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"

In android/settings.gradle :

include ':react-native-vector-icons'

project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')

Using

Now you want to plaster icons all over your application, don’t you? I do too!

If you are using Font Awesome like me, you would go to http://fontawesome.io/icons/ and pick your icon. You need the name without “fa-” at the beginning, and then you drop icon in your project like so:

import Icon from 'react-native-vector-icons/FontAwesome';

WEB

Because my project is isomorphic, this caused some problems in the web version. react-native-web is not yet compatible with react-native-vector-icons. To make the project even compile on the web, I made 3 files that abstracted the icon enough:

icon.web.js

import React from 'react';
import { Text } from 'react-native';
export default Text;

index.android.js and index.ios.js

import Icon from 'react-native-vector-icons/FontAwesome';
export default Icon;

Because isomorphism works in react native by picking specific files, my project now builds under all 3 environments. Granted, I would prefer it to build for web with working icons, which it does not do currently, but thats a battle for another day.

React Native Isomorphic app over the weekend

This article is part of my “React Native Isomorphic app over the weekend” series. It shares the problems I encountered during development of TeamParrot and Headstart Journal.

React Native Web & Isomorphic magic

One of the most appealing qualities of React Native is the idea of isomorphic code. Code, that can run on both iOS and Android, enabling you to write 2 apps at the same time.

But, there is another benefit to isomorphic code. You can also speed up writing your code just for one app.

Since react Native is using React code, I initially wanted to develop parts of the application “the React way”, using hot reloading, React Devtools and the fast feedback cycle that web development permits. That would let me work on the app without emulators and building the code.

It’s possible and much more.

React-Native-Web

‘react-native-web’ builts your react native app as a web app. Not pieces and snippets, but the whole app. Granted, there is a lot of functionality missing, so it’s almost certain that you will have to modify your source a bit to even make the build work, but there is one use-case that it’s perfect for:

UI components.

In TeamParrot I have separated all UI components as “dumb components” without much logic nor dependencies. That way, I can develop UI separately from the whole app. I have made specific container-component for web that holds all of these dumb components and allows me to see all of them in one place, iterate and work on in my web browser.

Let’s do this!

If you want this also for your app, here is how I’ve done it for TeamParrot:

React-Native-Web-Starter

I used react native web boilerplate called react-native-web-starter to get started. I downloaded the latest master, dumped the files into root of TeamParrot project and… Build failed. Now web, ios, and android build stopped working.

Now, let’s fix it!

First need proper libraries in your package.json devDependencies:

"react-native-web": "^0.0.39",
"babel-core": "6.9.1",
"babel-loader": "6.2.4",
"babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-preset-es2015": "6.9.0",
"babel-preset-react": "6.5.0",
"babel-preset-stage-1": "6.5.0",
"babel-runtime": "6.9.2",
"webpack": "1.13.1",
"webpack-dev-server": "1.14.1"

You need to remove .babelrc provided by react-native-web-starter, since that will interfere with RN babel build process. But the configuration from .babelrc needs to end up somewhere, so you need to modify your package.json loader to include:

{
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    query: {
        cacheDirectory: true,
        "presets": [
            "es2015",
            "stage-1",
            "react"
        ],
        "plugins": [
            "transform-decorators-legacy"
        ]
    },
}

Everything seems ok at this moment, but the app still does not want to load. After debugging for a while it turned out that these components didnt want to play nice with React Native Web:

  • Switch used for toggles
  • Icon from `react-native-vector-icons`
  • RefreshControl used for “Pull to refresh” on iOS.

I decided on a n ugly solution that I would just render “Text” instead of Icon and not render Switch or RefreshControl at all by passing “webDevelopment” prop:


export default function MainView( { queue, playing, channels, stop, pause, play, refresh, loading, header, openDrawer, webDevelopment = false } ) {
	const msg = get( queue, [ 0, 'items', 0 ] );
	const scrollProps = { style: { flex: 4 } };
	if ( ! webDevelopment ) {
		//When building this component inside react-native-web, RefreshControl does not work correctly
		scrollProps[ 'refreshControl' ] = ;
	}

The last problem I needed to solve was loading images. React Native has a an Image component, to which you pass local images by require prop:

To make this work on web, with webpack, you need to install file-loader:

npm install –save-dev file-loader

Webpackconfig

it’s located under /web/webpack.config.dev.js :


const path = require('path')
const webpack = require('webpack')

const DIRECTORY = path.join(__dirname)

module.exports = {
    devServer: {
        contentBase: path.join(__dirname, 'src')
    },
    entry: [
        path.join(__dirname, '../index.web.js')
    ],
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                query: {
                    cacheDirectory: true,
                    "presets": [
                        "es2015",
                        "stage-1",
                        "react"
                    ],
                    "plugins": [
                        "transform-decorators-legacy"
                    ]
                }
            },
            {
                test: /\.(png|jpg|gif)$/,
                loader: "file-loader?name=img/img-[hash:6].[ext]"
            }
        ]
    },
    output: {
        filename: 'bundle.js'
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
        }),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.OccurenceOrderPlugin()
    ],
    resolve: {
        alias: {
            'react-native': 'react-native-web'
        }
    }
}

Actual web code

This is my index.web.js:


import { AppRegistry } from 'react-native'; import Screens from './web/screens';

AppRegistry.registerComponent('App', () => Screens); AppRegistry.runApplication('App', { rootTag: document.getElementById('react-root') } );

Let’s roll!

And this is how I run my webpack dev server:

webpack-dev-server --port 3000 --config web/webpack.config.dev.js --inline --hot --colors --quiet

Zrzut ekranu 2016-11-12 o 19.37.20.png
Beauty of developing React Native app in devtools

Try it! It really speeds up the whole process!

React Native Isomorphic app over the weekend

This article is part of my “React Native Isomorphic app over the weekend” series. It shares the problems I encountered during development of TeamParrot and Headstart Journal.

React Native: Oauth

If you want to use any self-respecting API on the web, you are going to need Oauth authentication. If you need an explanation what oauth is, there are plenty better ones. These are the steps your app needs to go through:

  • Send user to special URL in the browser that lets them log in to the service
  • Handle callback URL from this service
  • Exchange keys with the service to make sure callback was legit
  • Store the token.

Since I used Slack API from the slack npm package, I had the “Exchanging the keys” parts ready.

0. Preparing the app

You thought we were going to start from 1, didn’t you? Well, you you must be new to this “programming” stuff. 😃  On the more serious note, you need to register your app in the API provider to get special keys for the step 1 and fill in URL for the step 2.

For TeamParrot, I set up following OAuth redirect URL:

Zrzut ekranu 2016-10-30 o 16.29.31.png

1.Send a user to the special URL

You will need to use “Linking” library. DO NOT confuse Library called “Linking” with the process of “Linking libraries”, which is tying native libraries to JS code. I just send my users to 'https://slack.com/oauth/authorize?client_id=' + client_id + '&scope=channels:read%20channels:history%20users:read%20channels:write' and that opens web browser with appropriate “back” button.

2. Handling callback URL

If all goes good and your users give you appropriate permissions, they will be sent to the URL you specified in step 0. You may notice that this step has a weird protocol (team-parrot://). It actually can have any protocol you want that’s not used by an other app. This is enabled by “Deep linking” and “Intents” On android.

iOS

First, you need to configure your app to handle this specific link you want to capture. That tells the operating system to route those links to your beautiful and shiny app. As per usual, you have to do that in info.plist :

<string>com.piszek.teamparrot</string>
<key>CFBundleURLSchemes</key>
<array>
<string>team-parrot</string>
</array>

For a reason I completely do not understand, you also have to mess with a bit of Native code. More info in the docs

So, in file ios/SlackBird/AppDelegate.m :

@implementation AppDelegate
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
+  return [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
+}

Android

Deep linking is called “intents” in Android and much of the same concept applies. You have to tell the OS that you want to open certain protocol inside your app. So:

In android/app/src/main/AndroidManifest.xml

<activity android:name=".MainActivity"
+        android:launchMode="singleTask"
...
>
+        <intent-filter>
+          <action android:name="android.intent.action.VIEW" />
+          <category android:name="android.intent.category.DEFAULT" />
+          <category android:name="android.intent.category.BROWSABLE" />
+          <data android:scheme="team-parrot"/>
+      </intent-filter>

OAuth walkthrough

Now you are ready to become a Oauth Sith and step to the dark side. This is how you oauth:

Linking.openURL( 'https://slack.com/oauth/authorize?client_id=' + client_id + '&scope=channels:read%20channels:history%20users:read%20channels:write' );
Linking.addEventListener( 'url', handleUrl );

And now your function “handleUrl” will be responsible for confirming “CODE” and exchanging it for a token. Usually, that will be handled by your API library of choice. Here is how I did it for TeamParrot:

import { client_id, client_secret } from '../config/slack';
import exchangeCode from 'slack/methods/oauth.access';
import testAuth from 'slack/methods/auth.test';
import { Linking } from 'react-native';

export function login() {
    return new Promise( ( resolve, reject ) => {
        Linking.openURL( 'https://slack.com/oauth/authorize?client_id=' + client_id + '&scope=channels:read%20channels:history%20users:read%20channels:write' );
        Linking.addEventListener( 'url', handleUrl );
        function handleUrl( event ) {
            const error = event.url.toString().match( /error=([^&]+)/ );
            const code = event.url.toString().match( /code=([^&]+)/ );
            if ( error ) {
                reject( error[ 1 ] );
            } else if ( code ) {
                exchangeCode( { client_id, client_secret, code: code[ 1 ] }, ( err, data ) => err ? reject( err ) : resolve( data ) );
                Linking.removeEventListener( 'url', handleUrl );
            } else {
                reject( 'params' );
            }
        }
    } );
}

export function testLogin( account ) {
    return new Promise( ( resolve, reject ) => testAuth( { token: account.access_token }, ( err, data ) => {
        if ( err || data.user_id !== account.user_id ) {
            reject( err, data );
        } else {
            resolve( Object.assign( {}, account, data ) );
        }
    } ) );
}

So there you have it. Now, you should be authenticated over Oauth2 with the slack API and able to make calls.

React Native Isomorphic app over the weekend

This article is part of my “React Native Isomorphic app over the weekend” series. It shares the problems I encountered during development of TeamParrot and Headstart Journal.

  • Oauth
  • Vector Icons – FontAwesome
  • CodePush
  • Renaming an App
  • Web & Isomorphic code development in Chrome devTools
  • Whole story

Rootowanie HTC Desire

Witaj ludu!

Jakiś czas temu kupiłem sobie w PLAY maszynkę zwaną HTC Desire. Świetna sprawa, naprawdę gorąco polecam ten telefon i inne androidowe wynalazki. Jedyna wada po przesiadce z Xperia X1 to brak klawiatury sprzętowej i kiepski czas życia na baterii.

Z takim wyświetlaczem to nie dziwne.

Oraz BARDZO MAŁA ILOŚĆ MIEJSCA

Ale nie o tym miałem pisać.

Łatwe Rootowanie HTC Desire

Po  co?

Rootowanie to uzyskanie dostępu do konta administratora w telefonie. Generalnie, po wykonaniu tej operacji można sobie hulać i wyczyniać z telefonem różne rzeczy, Mamy na przykład pełny dostęp do shella.

Moją motywacją było usunięcie nieusuwalnych aplikacji takich jak:

  • Quickoffice
  • Stock
  • Footprints
  • Inne G.

Żeby rozwiązać ten problem małej ilości miejsca….

Przewodnik

Continue reading “Rootowanie HTC Desire”

Archiwizowanie rozmów telefonicznych

Moja praca wymaga bardzo dużej ilości kontaktów telefonicznych. Klienci często dyktują mi różne informacje, albo mówią różne rzeczy których potem nie pamiętają.. No nie jest czasem ciekawie

Idąc za przykładem naszego byłego ministra sprawiedliwości postanowiłem Uziobrowić swój telefon. Jako że posiadam tzw. HTC Wizarda (SPV M3000) i ROM WM6 Pathfinder 3.2 NxS, (czyli w skrócie – mój telefon to PDA z Windows Mobile), mogłem uruchomić na nim program PMRecorder, nagrywający wszystkie rozmowy. Program jest darmowy, jednak nagrywa rozmowy w dziwnym formacie.

Więc, do rzeczy. Napisałem program, który przekształci katalog z plikami PMRecordera i BSCallTimes.xml (plik z historią rozmów WM6) na pliki do otwarcia w MediaPlayerze a dane wrzuci do bazy mysql.

Jest to skrypt PHP. Wiem że to szalone, głupie i bezsensowne, ale lubię PHP, mam dostęp do serwera i odpalam go bezproblemowo w Shellu.

Warto zwiększyć limit czasu wykonywania i pamięci w php.ini
Schemat tabeli w bazie danych:

CREATE TABLE `owczarek_rozmowy` (`id` int(11) NOT NULL auto_increment,`wav_file` varchar(16) NOT NULL default '',`type` text NOT NULL,`time` int(12) NOT NULL default '0',
`length` int(6) NOT NULL default '0',
`number` varchar(12) NOT NULL default '',
`caller` text NOT NULL,
`number_type` char(1) NOT NULL default '',
`note` text NOT NULL,
PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

A oto właściwy skrypt:

$input="./input";

$sql_host='localhost';
$sql_user='artpi';
$sql_pass='5155';
$sql_base='owczarek';
$sql_table='owczarek_rozmowy';
$wav_archive="./archiwum";

//A great function i found at http://www.bin-co.com/php/scripts/xml2array/

function xml2array($contents, $get_attributes=1) {
    if(!$contents) return array();

    if(!function_exists('xml_parser_create')) {
        //print "'xml_parser_create()' function not found!";
        return array();
    }
    //Get the XML parser of PHP - PHP must have this module for the parser to work
    $parser = xml_parser_create();
    xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, 0 );
    xml_parser_set_option( $parser, XML_OPTION_SKIP_WHITE, 1 );
    xml_parse_into_struct( $parser, $contents, $xml_values );
    xml_parser_free( $parser );

    if(!$xml_values) return;//Hmm...

    //Initializations
    $xml_array = array();
    $parents = array();
    $opened_tags = array();
    $arr = array();

    $current = &$xml_array;

    //Go through the tags.
    foreach($xml_values as $data) {
        unset($attributes,$value);//Remove existing values, or there will be trouble
        extract($data);//We could use the array by itself, but this cooler.

        $result = '';
        if($get_attributes) {//The second argument of the function decides this.
            $result = array();
            if(isset($value)) $result['value'] = $value;

            //Set the attributes too.
            if(isset($attributes)) {
                foreach($attributes as $attr => $val) {
                    if($get_attributes == 1) $result['attr'][$attr] = $val; //Set all the attributes in a array called 'attr'
                    /**  :TODO: should we change the key name to '_attr'? Someone may use the tagname 'attr'. Same goes for 'value' too */
                }
            }
        } elseif(isset($value)) {
            $result = $value;
        }

        //See tag status and do the needed.
        if($type == "open") {//The starting of the tag ''
            $parent[$level-1] = &$current;

            if(!is_array($current) or (!in_array($tag, array_keys($current)))) { //Insert New tag
                $current[$tag] = $result;
                $current = &$current[$tag];

            } else { //There was another element with the same tag name
                if(isset($current[$tag][0])) {
                    array_push($current[$tag], $result);
                } else {
                    $current[$tag] = array($current[$tag],$result);
                }
                $last = count($current[$tag]) - 1;
                $current = &$current[$tag][$last];
            }

        } elseif($type == "complete") { //Tags that ends in 1 line ''
            //See if the key is already taken.
            if(!isset($current[$tag])) { //New Key
                $current[$tag] = $result;

            } else { //If taken, put all things inside a list(array)
                if((is_array($current[$tag]) and $get_attributes == 0)//If it is already an array...
                        or (isset($current[$tag][0]) and is_array($current[$tag][0]) and $get_attributes == 1)) {
                    array_push($current[$tag],$result); // ...push the new element into that array.
                } else { //If it is not an array...
                    $current[$tag] = array($current[$tag],$result); //...Make it an array using using the existing value and the new value
                }
            }

        } elseif($type == 'close') { //End of tag ''
            $current = &$parent[$level-1];
        }
    }

    return($xml_array);
} 

	mysql_connect($sql_host,$sql_user,$sql_pass);
	mysql_select_db($sql_base);

	//If there's a call log file in input directory.

	if(file_exists($input."/BSCallTimes.xml"))
	{

		echo "Processing ".$input."/BSCallTimes.xml file n";
		$fd=fopen($input."/BSCallTimes.xml","r");
		$content=fread($fd,filesize($input."/BSCallTimes.xml"));
		fclose($fd);

		$array=xml2array($content);

		$calls=$array['BSCallTimesLog']['Call'];
		for($i=0;$i
		{
			$call['type']=$calls[$i]['attr']['Type'];
			$call['length']=$calls[$i]['attr']['RoundSeconds'];

			// If it's an sms, let's put size instead of length.
			if(substr($call['type'],0,3)=='msg')
				$call['length']=$calls[$i]['attr']['Amount'];

			//Time to parse date & time
			$date=explode("/",$calls[$i]['attr']['Date']);
			$time=explode(".",$calls[$i]['attr']['Time']);
			$time2=explode(":",$time[0]);
			$call['time']=mktime($time2[0],$time2[1],$time[1],$date[1],$date[2],$date[0]);

			$call['number']=$calls[$i]['Number']['value'];
			$call['caller']=$calls[$i]['Desc']['value'];

			//What the hell do we need ? for?
			if($call['caller']=='?')
				$call['caller']='';

			$wynik=mysql_query("SELECT * FROM $sql_table WHERE time='".$call['time']."';");

			if(mysql_num_rows($wynik)>0)
			{
				//Is it already in the databese, but maybe we put it during call record parsing.
				$wiersz=mysql_fetch_array($wynik);

				if(strlen($wiersz['type'])<4)
				{

					//Yup, we can update the data.
					mysql_query("UPDATE $sql_table SET type='".$call['type']."',length='".$call['length']."',number='".$call['number']."' WHERE time='".$call['time']."';");
					echo "At:".time()." updated call at [".$call['time']."] - Type:[".$call['type']."], Length:[".$call['length']."], Number: [".$call['number']."]n";
				}

			}
			else
			{
					//Lets insert some data.
					mysql_query("INSERT INTO $sql_table SET type='".$call['type']."',length='".$call['length']."',number='".$call['number']."',time='".$call['time']."',caller='".$call['caller']."';");
					echo "At: ".time()." inserted call at [".$call['time']."] - Type:[".$call['type']."], Length:[".$call['length']."], Number: [".$call['number']."], Caller: [".$call['caller']."]n";			

			}

		}
		echo "BSCallTimes Processed.n";
	}

	//So, now that BSCallTimes file is processed, let's process other files.

		$directory = opendir($input);
 		while($file = readdir($directory)) {

			//If it's PMRecorder file:
 			If(substr($file,-2)=='wv')
			{

				$naz=explode(".",$file);

				//So we don't do this again:
				if(!file_exists($wav_archive."/".$naz[0].".wav"))
				{

					$pliczek=$input."/".$file;

					echo "Processing file ".$pliczek."n";

					$fd=fopen($pliczek,"r");
					$content=fread($fd,filesize($pliczek));
					fclose($fd);

					$wav=strstr($content,"RIFF");

					$fd=fopen($wav_archive."/".$naz[0].".wav","w");
					fwrite($fd,$wav);
					fclose($fd);

					echo "Wav file saved at ".$wav_archive."/".$naz[0].".wav"."n";

					$naglowki=substr($content,24,strpos($content,"RIFF")-24);
					$nag=explode(chr(0).chr(0).chr(100),$naglowki);

					$czysc1=array(chr(0),chr(100));
					$czysc2=array("e","I","O","f",chr(0),chr(127));

					//PMRecorder file name
					$data[0]=$naz[0];
					//Unix time
					$data[1]=mktime($naz[0][8].$naz[0][9],$naz[0][10].$naz[0][11],$naz[0][12].$naz[0][13],$naz[0][0].$naz[0][1],$naz[0][2].$naz[0][3],$naz[0][4].$naz[0][5].$naz[0][6].$naz[0][7]);
					//Caller name
					$data[2]=str_replace($czysc1,"",$nag[0]);
					//Caller number
					$data[3]=str_replace($czysc2,"",$nag[1]);
					preg_match ("#([a-z])#is",$data[3],$tmp);
					//Number type (h/m/...)
					$data[4]=$tmp[1];
					//Filtered number
					$data[3]=preg_replace("#([a-z])#is","",$data[3]);

					//Once again some sql:

					$wynik=mysql_query("SELECT * FROM $sql_table WHERE time >'".($data[1]-4)."' AND time<'".($data[1]+1)."';");

					//Why so strange where clause? PMRecorder is a bit retarded in comparison with BSCAlltimes 🙂		

					if(mysql_num_rows($wynik)>0)
					{
						//Is it already in the databese, but maybe we put it during call record parsing.
						$wiersz=mysql_fetch_array($wynik);

						if(strlen($wiersz['wav_file'])<1)
						{
							//Yup, we can update the data.
							mysql_query("UPDATE $sql_table SET wav_file='".$data[0]."',number_type='".$data[4]."',caller='".$data[2]."' WHERE time>'".($data[1]-4)."' AND time<'".($data[1]+1)."';");
							echo "At:".time()." updated call at [".$data[1]."] - Wav file:[".$data[0]."], Caller:[".$data[2]."],Number type:[".$data[4]."],n";
						}

					}
					else
					{
							//Call wasnt inserted during bscalltimes file parsing. Let's do this!
							//Lets insert some data.
							mysql_query("INSERT INTO $sql_table SET wav_file='".$data[0]."',number_type='".$data[4]."',caller='".$data[2]."',time='".$data[1]."',number='".$data[3]."';");
							echo "At:".time()." inserted call at [".$data[1]."] - Wav file:[".$data[0]."], Caller:[".$data[2]."],Number type:[".$data[4]."],Number:[".$data[3]."]n";		

					}

					echo "File ".$pliczek." processed.n";
				}

			}

 		}
		closedir($directory);

print($log);