суббота, 30 июня 2012 г.

Транзит из аэропорта Сент-Экзюпери, Лион, Франция

Из Лионского аэропорта Сент-Экзюпери до города удобнее всего добираться на трамвае-экспрессе Rhônexpress. Это выходит примерно вдвое дешевле чем на такси. Отправление раз в полчаса. Очень комфортабельный и шустрый, куча отделений для багажа. Информационные мониторы в салоне показывают табло прилета-вылета аэропорта. В процессе путешествия делает 2 остановки в пригороде.


Билет брать необходимо, проверяют всегда. На вокзале Part-Dieu тикет-автоматы располагаются непосредственно на остановке (её легко найти по указателям вокзала), в аэропорту - в зале Gare Railway Station, не спускаясь на платформу. Они красного цвета с надписью Rhônexpress.  Поезда в одну сторону стоит 14€. Дети до 12 лет бесплатно, от 12 до 25 лет - 11.50€.

Автоматы по продаже билетов бывают с и без купюроприемника.
Печально, что в аэропортовом билетном автомате не прошла моя чипованная VISA CLASSIC, которая отлично проходила везде и всегда. Поэтому для покупки тикета лучше запастись мелкими купюрами (до 50€) и монетами. Автомат выдает сдачу.

В пользовании автомат довольно прост. Кнопки по сторонам экрана позволяют выбрать варианты, кнопки ДА/Отмена внизу ну вы понимаете зачем. Необходимо выбрать тип билета - обычно это Aller-Simple (в одну сторону), детские билеты можно узнать и на французском - они с соответствующими цифрами 12-25. До 12 лет, как я уже говорил, билет брать не нужно. Выбираем, затем справа первая и вторая кнопка - количество, затем подтвержаем выбор и вставляем купюры и пластиковую карту.
Билет, к сожалению, не сохранился. Он на обычной бумаге, напоминает кассовый чек. Вместе с ним может отпечататься чек банковской карты, не перепутайте их и не выбросьте билет!

С момента покупки билета до посадки должно пройти до 45 минут! Иначе прийдется покупать новый билет. Спускаемся на платформу, располагаемся, ждем кондуктора, наслаждаемся видом.

вторник, 26 июня 2012 г.

Создание больших баз данных в Android

Устал воевать с парсером Blogspot, поэтому выкладываю как есть

Основным способом создания баз данных для Android является создание базы данных командами SQL. Это не так быстро, но скорее всего пользователю это будет незаметно, если в базе таблиц немного. Альтернатива данному методу - это создание базы данных в любом SQLite редакторе с последующим помещением файла базы в папку Assets проекта, а затем копировании оттуда. Это намного эффективнее, если база данных содержит много таблиц и других компонентов. Обновления можно производить изменяя версию базы в файле strings.xml. Обновление производится путём копирования новой базы поверх старой, старая база сохраняется под другим именем. Можно создать первоначальную базу с помощью плагина Firefox SQLite Manager, либо любым другим редактором (SQLite Expert Personal например).

В приведенных ниже примерах показано, как использовать оба метода.

Вот  пример файла create_database.sql. Он должен быть помещен в директорию Assets проекта
--Android requires a table named 'android_metadata' with a 'locale' column
CREATE TABLE "android_metadata" ("locale" TEXT DEFAULT 'en_US');
INSERT INTO "android_metadata" VALUES ('en_US');

CREATE TABLE "kitchen_table";
CREATE TABLE "coffee_table";
CREATE TABLE "pool_table";
CREATE TABLE "dining_room_table";
CREATE TABLE "card_table"; 

А вот пример скрипта update_database.sql. Обратите внимание, что все три типа SQL-комментариев игнорируются парсером SQL, включенном в данный пример
--CREATE TABLE "kitchen_table";  This is one type of comment in sql.  It is ignored by parseSql.
/*
 * CREATE TABLE "coffee_table"; This is a second type of comment in sql.  It is ignored by parseSql.
 */
{
CREATE TABLE "pool_table";  This is a third type of comment in sql.  It is ignored by parseSql.
}
/* CREATE TABLE "dining_room_table"; This is a second type of comment in sql.  It is ignored by parseSql. */
{ CREATE TABLE "card_table"; This is a third type of comment in sql.  It is ignored by parseSql. }

--DROP TABLE "picnic_table"; Uncomment this if picnic table was previously created and now is being replaced.
CREATE TABLE "picnic_table" ("plates" TEXT);
INSERT INTO "picnic_table" VALUES ('paper');



Добавляем запись в /res/values/strings.xml о версии базы данных:


<item type="string" name="databaseVersion" format="integer">1</item>

Вот класс который инициализирует и использует базу данных (код можно запустить в отдельном потоке, если он использует много ресурсов):


package android.example;
import android.app.Activity;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
/**
 * @author Danny Remington - MacroSolve
 * 
 *         Activity for demonstrating how to use a sqlite database.
 */
public class Database extends Activity {
     /** Called when the activity is first created. */
     @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        DatabaseHelper myDbHelper;
        SQLiteDatabase myDb = null;

        myDbHelper = new DatabaseHelper(this);
        /*
         * Database must be initialized before it can be used. This will ensure
         * that the database exists and is the current version.
         */
         myDbHelper.initializeDataBase();

         try {
            // A reference to the database can be obtained after initialization.
            myDb = myDbHelper.getWritableDatabase();
            /*
             * Place code to use database here.
             */
         } catch (Exception ex) {
            ex.printStackTrace();
         } finally {
            try {
                myDbHelper.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                myDb.close();
            }
        }

    }
}




Приведем пример DatabaseHelper, которы может создавать и обновлять базу при необходимости (ПРИМЕЧАНИЕ:. Android требует создания класса, который расширяет SQLiteOpenHelper для того, чтобы работать с базой данных SQLite):


package android.example;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
 * @author Danny Remington - MacroSolve
 * 
 *         Helper class for sqlite database.
 */
public class DatabaseHelper extends SQLiteOpenHelper {

    /*
     * The Android's default system path of the application database in internal
     * storage. The package of the application is part of the path of the
     * directory.
     */
    private static String DB_DIR = "/data/data/android.example/databases/";
    private static String DB_NAME = "database.sqlite";
    private static String DB_PATH = DB_DIR + DB_NAME;
    private static String OLD_DB_PATH = DB_DIR + "old_" + DB_NAME;

    private final Context myContext;

    private boolean createDatabase = false;
    private boolean upgradeDatabase = false;

    /**
     * Constructor Takes and keeps a reference of the passed context in order to
     * access to the application assets and resources.
     * 
     * @param context
     */
    public DatabaseHelper(Context context) {
        super(context, DB_NAME, null, context.getResources().getInteger(
                R.string.databaseVersion));
        myContext = context;
        // Get the path of the database that is based on the context.
        DB_PATH = myContext.getDatabasePath(DB_NAME).getAbsolutePath();
    }

    /**
     * Upgrade the database in internal storage if it exists but is not current. 
     * Create a new empty database in internal storage if it does not exist.
     */
    public void initializeDataBase() {
        /*
         * Creates or updates the database in internal storage if it is needed
         * before opening the database. In all cases opening the database copies
         * the database in internal storage to the cache.
         */
        getWritableDatabase();

        if (createDatabase) {
            /*
             * If the database is created by the copy method, then the creation
             * code needs to go here. This method consists of copying the new
             * database from assets into internal storage and then caching it.
             */
            try {
                /*
                 * Write over the empty data that was created in internal
                 * storage with the one in assets and then cache it.
                 */
                copyDataBase();
            } catch (IOException e) {
                throw new Error("Error copying database");
            }
        } else if (upgradeDatabase) {
            /*
             * If the database is upgraded by the copy and reload method, then
             * the upgrade code needs to go here. This method consists of
             * renaming the old database in internal storage, create an empty
             * new database in internal storage, copying the database from
             * assets to the new database in internal storage, caching the new
             * database from internal storage, loading the data from the old
             * database into the new database in the cache and then deleting the
             * old database from internal storage.
             */
            try {
                FileHelper.copyFile(DB_PATH, OLD_DB_PATH);
                copyDataBase();
                SQLiteDatabase old_db = SQLiteDatabase.openDatabase(OLD_DB_PATH, null, SQLiteDatabase.OPEN_READWRITE);
                SQLiteDatabase new_db = SQLiteDatabase.openDatabase(DB_PATH,null, SQLiteDatabase.OPEN_READWRITE);
                /*
                 * Add code to load data into the new database from the old
                 * database and then delete the old database from internal
                 * storage after all data has been transferred.
                 */
            } catch (IOException e) {
                throw new Error("Error copying database");
            }
        }

    }

    /**
     * Copies your database from your local assets-folder to the just created
     * empty database in the system folder, from where it can be accessed and
     * handled. This is done by transfering bytestream.
     * */
    private void copyDataBase() throws IOException {
        /*
         * Close SQLiteOpenHelper so it will commit the created empty database
         * to internal storage.
         */
        close();

        /*
         * Open the database in the assets folder as the input stream.
         */
        InputStream myInput = myContext.getAssets().open(DB_NAME);

        /*
         * Open the empty db in interal storage as the output stream.
         */
        OutputStream myOutput = new FileOutputStream(DB_PATH);

        /*
         * Copy over the empty db in internal storage with the database in the
         * assets folder.
         */
        FileHelper.copyFile(myInput, myOutput);

        /*
         * Access the copied database so SQLiteHelper will cache it and mark it
         * as created.
         */
        getWritableDatabase().close();
    }

    /*
     * This is where the creation of tables and the initial population of the
     * tables should happen, if a database is being created from scratch instead
     * of being copied from the application package assets. Copying a database
     * from the application package assets to internal storage inside this
     * method will result in a corrupted database.
     * 
     * NOTE: This method is normally only called when a database has not already
     * been created. When the database has been copied, then this method is
     * called the first time a reference to the database is retrieved after the
     * database is copied since the database last cached by SQLiteOpenHelper is
     * different than the database in internal storage.
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        /*
         * Signal that a new database needs to be copied. The copy process must
         * be performed after the database in the cache has been closed causing
         * it to be committed to internal storage. Otherwise the database in
         * internal storage will not have the same creation timestamp as the one
         * in the cache causing the database in internal storage to be marked as
         * corrupted.
         */
        createDatabase = true;

        /*
         * This will create by reading a sql file and executing the commands in
         * it.
         */
            // try {
            // InputStream is = myContext.getResources().getAssets().open(
            // "create_database.sql");
            //
            // String[] statements = FileHelper.parseSqlFile(is);
            //
            // for (String statement : statements) {
            // db.execSQL(statement);
            // }
            // } catch (Exception ex) {
            // ex.printStackTrace();
            // }
    }

    /**
     * Called only if version number was changed and the database has already
     * been created. Copying a database from the application package assets to
     * the internal data system inside this method will result in a corrupted
     * database in the internal data system.
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        /*
         * Signal that the database needs to be upgraded for the copy method of
         * creation. The copy process must be performed after the database has
         * been opened or the database will be corrupted.
         */
        upgradeDatabase = true;

        /*
         * Code to update the database via execution of sql statements goes
         * here.
         */

        /*
         * This will upgrade by reading a sql file and executing the commands in
         * it.
         */
        // try {
        // InputStream is = myContext.getResources().getAssets().open(
        // "upgrade_database.sql");
        //
        // String[] statements = FileHelper.parseSqlFile(is);
        //
        // for (String statement : statements) {
        // db.execSQL(statement);
        // }
        // } catch (Exception ex) {
        // ex.printStackTrace();
        // }
    }

    /**
     * Called everytime the database is opened by getReadableDatabase or
     * getWritableDatabase. This is called after onCreate or onUpgrade is
     * called.
     */
    @Override
    public void onOpen(SQLiteDatabase db) {
        super.onOpen(db);
    }

    /*
     * Add your public helper methods to access and get content from the
     * database. You could return cursors by doing
     * "return myDataBase.query(....)" so it'd be easy to you to create adapters
     * for your views.
     */
}




Класс FileHelper содержит небходимые методы для копирования файлов и анализа SQL скриптов:


package android.example;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.nio.channels.FileChannel;
/**
 * @author Danny Remington - MacroSolve
 * 
 *         Helper class for common tasks using files.
 * 
 */
public class FileHelper {
    /**
     * Creates the specified toFile that is a byte for byte a copy
     * of fromFile. If toFile already existed, then
     * it will be replaced with a copy of fromFile. The name and
     * path of toFile will be that of toFile. Both
     * fromFile and toFile will be closed by this
     * operation.
     * 
     * @param fromFile
     *            - InputStream for the file to copy from.
     * @param toFile
     *            - InputStream for the file to copy to.
     */
    public static void copyFile(InputStream fromFile, OutputStream toFile) throws IOException {
        // transfer bytes from the inputfile to the outputfile
        byte[] buffer = new byte[1024];
        int length;

        try {
            while ((length = fromFile.read(buffer)) > 0) {
                toFile.write(buffer, 0, length);
            }
        }
        // Close the streams
        finally {
            try {
                if (toFile != null) {
                    try {
                        toFile.flush();
                    } finally {
                        toFile.close();
                    }
            }
            } finally {
                if (fromFile != null) {
                    fromFile.close();
                }
            }
        }
    }

    /**
     * Creates the specified toFile that is a byte for byte a copy
     * of fromFile. If toFile already existed, then
     * it will be replaced with a copy of fromFile. The name and
     * path of toFile will be that of toFile. Both
     * fromFile and toFile will be closed by this
     * operation.
     * 
     * @param fromFile
     *            - String specifying the path of the file to copy from.
     * @param toFile
     *            - String specifying the path of the file to copy to.
     */
    public static void copyFile(String fromFile, String toFile) throws IOException {
        copyFile(new FileInputStream(fromFile), new FileOutputStream(toFile));
    }

    /**
     * Creates the specified toFile that is a byte for byte a copy
     * of fromFile. If toFile already existed, then
     * it will be replaced with a copy of fromFile. The name and
     * path of toFile will be that of toFile. Both
     * fromFile and toFile will be closed by this
     * operation.
     * 
     * @param fromFile
     *            - File for the file to copy from.
     * @param toFile
     *            - File for the file to copy to.
     */
    public static void copyFile(File fromFile, File toFile) throws IOException {
        copyFile(new FileInputStream(fromFile), new FileOutputStream(toFile));
    }

    /**
     * Creates the specified toFile that is a byte for byte a copy
     * of fromFile. If toFile already existed, then
     * it will be replaced with a copy of fromFile. The name and
     * path of toFile will be that of toFile. Both
     * fromFile and toFile will be closed by this
     * operation.
     * 
     * @param fromFile
     *            - FileInputStream for the file to copy from.
     * @param toFile
     *            - FileInputStream for the file to copy to.
     */
    public static void copyFile(FileInputStream fromFile, FileOutputStream toFile) throws IOException {
        FileChannel fromChannel = fromFile.getChannel();
        FileChannel toChannel = toFile.getChannel();

        try {
            fromChannel.transferTo(0, fromChannel.size(), toChannel);
        } finally {
            try {
                if (fromChannel != null) {
                    fromChannel.close();
                }
            } finally {
                if (toChannel != null) {
                    toChannel.close();
                }
            }
        }
    }

    /**
     * Parses a file containing sql statements into a String array that contains
     * only the sql statements. Comments and white spaces in the file are not
     * parsed into the String array. Note the file must not contained malformed
     * comments and all sql statements must end with a semi-colon ";" in order
     * for the file to be parsed correctly. The sql statements in the String
     * array will not end with a semi-colon ";".
     * 
     * @param sqlFile
     *            - String containing the path for the file that contains sql
     *            statements.
     * 
     * @return String array containing the sql statements.
     */
    public static String[] parseSqlFile(String sqlFile) throws IOException {
        return parseSqlFile(new BufferedReader(new FileReader(sqlFile)));
    }

    /**
     * Parses a file containing sql statements into a String array that contains
     * only the sql statements. Comments and white spaces in the file are not
     * parsed into the String array. Note the file must not contained malformed
     * comments and all sql statements must end with a semi-colon ";" in order
     * for the file to be parsed correctly. The sql statements in the String
     * array will not end with a semi-colon ";".
     * 
     * @param sqlFile
     *            - InputStream for the file that contains sql statements.
     * 
     * @return String array containing the sql statements.
     */
    public static String[] parseSqlFile(InputStream sqlFile) throws IOException {
        return parseSqlFile(new BufferedReader(new InputStreamReader(sqlFile)));
    }

    /**
     * Parses a file containing sql statements into a String array that contains
     * only the sql statements. Comments and white spaces in the file are not
     * parsed into the String array. Note the file must not contained malformed
     * comments and all sql statements must end with a semi-colon ";" in order
     * for the file to be parsed correctly. The sql statements in the String
     * array will not end with a semi-colon ";".
     * 
     * @param sqlFile
     *            - Reader for the file that contains sql statements.
     * 
     * @return String array containing the sql statements.
     */
    public static String[] parseSqlFile(Reader sqlFile) throws IOException {
        return parseSqlFile(new BufferedReader(sqlFile));
    }

    /**
     * Parses a file containing sql statements into a String array that contains
     * only the sql statements. Comments and white spaces in the file are not
     * parsed into the String array. Note the file must not contained malformed
     * comments and all sql statements must end with a semi-colon ";" in order
     * for the file to be parsed correctly. The sql statements in the String
     * array will not end with a semi-colon ";".
     * 
     * @param sqlFile
     *            - BufferedReader for the file that contains sql statements.
     * 
     * @return String array containing the sql statements.
     */
    public static String[] parseSqlFile(BufferedReader sqlFile) throws IOException {
        String line;
        StringBuilder sql = new StringBuilder();
        String multiLineComment = null;

        while ((line = sqlFile.readLine()) != null) {
            line = line.trim();

            // Check for start of multi-line comment
            if (multiLineComment == null) {
                // Check for first multi-line comment type
                if (line.startsWith("/*")) {
                    if (!line.endsWith("}")) {
                        multiLineComment = "/*";
                    }
                // Check for second multi-line comment type
                } else if (line.startsWith("{")) {
                    if (!line.endsWith("}")) {
                        multiLineComment = "{";
                }
                // Append line if line is not empty or a single line comment
                } else if (!line.startsWith("--") && !line.equals("")) {
                    sql.append(line);
                } // Check for matching end comment
            } else if (multiLineComment.equals("/*")) {
                if (line.endsWith("*/")) {
                    multiLineComment = null;
                }
            // Check for matching end comment
            } else if (multiLineComment.equals("{")) {
                if (line.endsWith("}")) {
                    multiLineComment = null;
                }
            }

        }

        sqlFile.close();

        return sql.toString().split(";");
    }
}

четверг, 14 июня 2012 г.

Получение визы Франции

Итак, получил на днях Шенгенскую визу и теперь еду отдыхать с семьей по маршруту Москва-Лион-Сан-Рафаэль-Лион-Москва. С вариациями в зависимости от культурной программы.

Цена


Визы на семью (два взрослых и ребенок 6 лет) обошлась в 7500 руб, из них: стоимость визы 3х35 EUR, остальное - консульский сбор.

Особенности


Особенности получения визы во Французском сервисно-визовом центре.
- Предварительная онлайн-запись на любую дату. Можно и без нее, но придется чуть больше постоять. Для Германской мне пришлось выложить 400 рублей за звонок для записи (2 минуты разговора).
- Срок оформления - от 5 до 10 рабочих дней
- Необходимо ксерить все страницы гражданского паспорта для каждого комплекта документов, где он необходим (допустим, вы являетесь спонсором кого-либо - ксерьте еще раз для него). Эту копию вернули потом вместе с паспортом.
- Лучше предварительно разобрать по стопкам документы на каждого члена семьи. 
- Очень культурно и цивильно. Зал ожидания, электронная очередь.

Комплект документов


Для своей семьи я являюсь единственным источником доходом, поэтому мой комплект документов:
  •  три анкеты на шенген (моя, супруги, сына). По две фотки - одну наклеиваем, вторую оставляем рядом.
Бланк анкеты:
На английском языке, 
На французском языке
Я на английском заполнял. Образец и порядок заполнения ищите в гугле, там полно
Требования к фотографиям. В-принципе, знают в любой фотозабегаловке, надо сказать заветные слова - "фото на шенген".


  • копия первой страницы загранпаспорта - разобрать по комплектам
  • копия туристических страховок - разобрать по комплектам
  • копия всех страниц внутреннего паспорта, даже пустых! Сделать дополнительные копии для каждого спонсируемого
  • справка 2-НДФЛ, справка с места работы. По идее можно обойтись одной из них, я сделал две.
  •  выписка с текущего банковского счета (выписка обычно дается бесплатно, надо попросить поставить на ней печать, а справка обычно за деньги - около 200 руб.). Не распечатка транзакций, а состояние на данный момент.
Актуально стоит вопрос о сумме доходов.
У меня в банковской выписке была указана сумма, достаточная для двух человек, но недостаточная для трех - прокатило. Из расчета 50 евро в день на человека.
  • бронирование отелей - одна копия на всех
  • авиабилеты - разобрать по комплектам
  • спонсорские письма на каждого спонсируемого члена :) Нотариально не заверять. Образец письма ниже.
  • подтверждение родства для каждого спонсируемого - свидетельство о рождении, свидетельство о браке
  • Перекрестная доверенность на вывоз ребенка (она так и называется). В конторе предлагают сделать две отдельных - не надо. Нужно одна общая. Стоит 800-1000 руб по ценам на 2012 год.
  • Заполненное согласие на обработку персональных данных. Для ребенка заполняет один из родителей, в реквизиты доверенности или иного документа, подтверждающего полномочия представителя нужно записать "свидетельство о рождении серия __ номер ________". 


Спросили, почему анкета с черной эмблемой а не синей. Сказал, что такая на сайте. Прошло.
По факту принятия документов поросят подписать бумагу а также агентский договор.



P.S. Образец спонсорского письма:


Спонсорское письмо


Я, Сергей Серафимович Пупкин, (дата рождения 14 июня 1960 года, паспорт 26 18 216841, зарегистрированный по адресу Московская область, город Люберцы, ул.Ленина, дом 1, квартира 12), являюсь спонсором поездки своей супруге, Зинаиде Ивановне Пупкиной (дата рождения 14 мая 1964 года, паспорт 36 13 193632, зарегистрированной по адресу Московская область, город Люберцы, улица Кирова, дом 2, квартира 10) и данным письмом гарантирую оплату всех расходов, связанных с пребыванием Зинаиды Ивановны Пупкиной на территории Французской Республики в период с 30 июля 2013 года по 14 августа 2013 года.


Пупкин С.С.
Телефон +7 (926) 324-14-29
pupkin.s.s.@gmail.com