вторник, 29 апреля 2014 г.

How to по git. Для склеротиков.

Настройка Git клиента для идентификации пользователя:
git config --global user.name "Your Name"
git config --global user.email "your@email.address"
git config --global core.editor "your editor"
Инициализация репозитория в текущем каталоге:
git init
Создает репозиторий в указанном каталоге:
git init <directory>
Клонирование репозитория на локальную машину:
git clone <repo>
Добавить файл для идексации и следить за всеми изменениями в нем ( можно задавать по маске ):
git add <file>
Добавить каталог для индексации:
git add <directory>
Удалить файл из индексации ( можно задавать по маске ):
git rm <file>
Перенести или переименовать файл / каталог:
git mv <source> <destination>
Сделать "снапшот" всех выполненных изменений:
git commit -a -m "message"
Статус репозитория ( добавление, удаление, изменение файлов ):
git status
Показать историю коммитов:
git log
Показать только определенное количество коммитов:
git log -n <limit>
Показать историю коммитов по конкретному файлу:
git log <file>
Посмотреть различия между последним коммитом и текущими изменениями:
git diff
Просмотр всех различий между коммитами:
git diff <commit> <commit>
Посмотреть различия между коммитом и текущими изменениями:
git diff <commit> <file>
Посмотреть различия между коммитами для файла:
git diff <commit> <commit> <file>
Показать список всех файлов в основной ветке:
git ls-tree master -r --name-only
Работа с ветками

Список веток:
git branch
Создание ветки:
git branch <new_branch_name>
Безопастное удаление ветки, если не были сделаны изменения:
git branch -d <branch_name>
Удаление ветки, даже если были сделаны изменения:
git branch -D <branch_name>
Переименование текущей ветки:
git branch -m <rename_current_branch>
Переход к существующей ветке:
git checkout <branch_name>
Создание и переход к ветке:
git checkout -b <new_branch_name>
Слияние текущей ветки с указанной:
git merge <branch>
Откат изменений

Перейти на последний коммит ветки "master":
git checkout master
Откатить изменения во всех файлах до указанного коммита:
git checkout <commit>
Откатить изменения для конкретного файла до указанного коммита:
git checkout <commit> <file>
Сделать откат всех изменений выполненных в коммите, при этом создается новый коммит указывающий на откат изменений:
git revert <commit>
Отмена изменений до последнего коммита, а также сбрасывает индексацию для конкретного файла:
git reset HEAD <file>
Отмена изменений до последнего коммита и сбрасывает индексацию:
git reset --soft
Отмена изменений до последнего коммита, сбрасывает индексацию, а так же отменить любые изменения в рабочей директории:
git reset --hard
programming Удаляет файлы которые не были добавлены в репозиторий:
git clean -f
Удаляет файлы которые не были добавлены в репозиторий по указанному пути:
git clean -f <path>
Удаляет файлы и каталоги которые не были добавлены в репозиторий:
git clean -df
Только показывает, что будет удаленно:
git clean -n
Работа с удаленными репозиториями

Список соединений с удаленными репозиториями:
git remote
Добавить соединение:
git remote add <name> <url>
Удалить соединение:
git remote rm <name>
Переименовать соединение:
git remote rename <old_name> <new_name>
Получить изменения из репозитория со списком всех веток ( при этом стираются любые локальные изменения ):
git fetch <remote>
Получить изменения из репозитория для конкретной ветки ( при этом стираются любые локальные изменения ):
git fetch <remote> <branch>
Получить копию текущей ветки с удаленного репозитория и слить ее с локальной копией:
git pull <remote>
Залить указанную ветку на удаленный репозиторий:
git push <remote> <branch>


Отдельно про git stash.
Удобно, занимаясь одним делом, «отложить» текущую работу в сторону и отвлечься, скажем, на срочное исправление бага, даже если он находится в другом бранче. После исправления и коммита можно преспокойно вернуться к начатому.
i. hack-hack-hack
ii. git stash
iii. fix-fix-fix
iv. git commit -a -m 'bugfix #31337'
v. git stash pop
Те же, кто знает про git stash, посмотрите на последнюю строку — её отличие от apply в том, что откладываемые результаты не остаются во временном хранилище (посмотрите git stash list после нескольких примений stash!)
Если изменение было фактически законченным, можно оформить коммит не отходят от кассы — git stash save 'commit msg' (взято с http://habrahabr.ru/post/75728/http://habrahabr.ru/post/137408/)
Отдельно про git bisect.
git bisect -  возможность бинарного поиска по коммитам. Для примера приведу следующую ситуацию. В нашем проекте поломался какой-то функционал, в чем конкретно проблема мы понять не можем, но точно знаем, что в прошлом релизе этот функционал точно работал. Так вот команда bisect позволяет нам легко откатить проект на заданное число коммитов и найти тот коммит, в котором неуловимый баг проявляется в первый раз.
Во-первых, запускаем механизм поиска и устанавливаем значение, что текущее состояние проекта нерабочее
git bisect start 
git bisect bad
Далее откатываемся на рабочее состояние проекта — на 10 коммитов назад по истории
git bisect good HEAD~10
Смотрим, проявляется ли этот баг в текущем состоянии. Допустим, сейчас с проектом все хорошо, поэтому продолжаем поиск дальше
git bisect good
Эта команда перематывает состояние проекта в середину, между хорошим коммитом и плохим, т. е. Сейчас мы находимся в пяти коммитах назад по истории. Если баг проявляется, то он находится до текущего коммита, если нет — то после. С каждой такой итерацией расстояние уменьшается в два раза, пока мы не находим плохой коммит (а главное — его автора, к сожалению, это его опять сделали мы).
После завершения поиска сбрасываем репозиторий в исходное состояние
git bisect reset )

пятница, 11 апреля 2014 г.

Используем Apache HttpClient или POST и GET для самых маленьких

Это будет небольшой пост в примерах с минимальным числом комментариев.

Задача: используя httpclient, отправить post - запрос с логином и паролем на некоторый адрес.
Решение: 
HttpClient httpclient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(SESSIONS_URL); //SESSIONS_URL - наш адрес
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair(StaticInformation.LOGIN,
LOGIN));
nameValuePairs.add(new BasicNameValuePair(
StaticInformation.PASSWORD, PASSWORD));
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httpPost, localContext);
int code = response.getStatusLine().getStatusCode(); //code - используем для проверки, прошел ли наш запрос
Пояснение: создаем клиент, создаем запрос, с помощью nameValuePairs забиваем необходимые параметры, делаем запрос, получаем код.

Задача: используя httpclient, отправить пост - запрос с логином и паролем на некоторый адрес при этом сохранить куку для дальнейших post(get) - запросов.
Решение: 
HttpClient httpclient = new DefaultHttpClient();
CookieStore cookieStore = new BasicCookieStore();
HttpContext localContext = new BasicHttpContext();
localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
HttpPost httpPost = new HttpPost(StaticInformation.SESSIONS_URL);
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair(StaticInformation.LOGIN,
LOGIN));
nameValuePairs.add(new BasicNameValuePair(
StaticInformation.PASSWORD, PASSWORD));

httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httpPost, localContext);
//делаем запрос номер два
HttpPost httpPostOffer = new HttpPost(StaticInformation.NEW_URL);
httpPostOffer.setEntity(<Сюда запишем новую сущность>);
response = httpclient.execute(httpPostOffer, localContext);
code = response.getStatusLine().getStatusCode();
Пояснение: по-сути то же самое, но тут мы должны создать cookieStore - хранилище для наших кук. Создаем и сразу же делаем привязку к контексту(localContext), после этого можно совершать запросы, передавая туда созданный localContext.

Задача: совершить запрос multipart/form-data. Отправить на сервер картинку, закодированную в Base64. Этим же запросом отослать остальную информацию.
Решение:
httpPostOffer.setEntity(complectEntityToPost(offer));

private MultipartEntity complectEntityToPost(Offer offer) {
MultipartEntity entity = new MultipartEntity(
HttpMultipartMode.BROWSER_COMPATIBLE);
// add data for multiform
for (String asset : offer.getAssets()) {
ByteArrayBody body = new ByteArrayBody(Base64.decode(asset,
Base64.DEFAULT), IMAGE_FORMAT,
Converter.currentDateToString()); //добавим данные как на форме
entity.addPart("offer[assets][]", body);
}
// add other data
for (NameValuePair pair : complectPostValuePairs(offer)) {
try {
entity.addPart(pair.getName(), new StringBody(pair.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
return entity;
}

private List<NameValuePair> complectPostValuePairs(Offer offer) {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();

nameValuePairs
.add(new BasicNameValuePair("offer[organizationId]", "1"));
nameValuePairs.add(new BasicNameValuePair("offer[cityId]", "16"));
               ...................
}
Пояснение: для  multipart/form-data запросов используется MultipartEntity. С помощью ByteArrayBody можно делать обертки вокруг конструкций типа 
Content-Disposition: form-data; name="offer[assets][]"; filename="icon.png",
передавая необходимые параметры в конструктор. Entity, используемая в предыдущих примерах, лишь часть MultipartEntity, поэтому добавлять параметры можно с помощью метода addPart, где первый параметр - имя параметра, второй параметр - его значение.

Задача: сделать так, чтобы при отправке  multipart/form-data - запроса на сервере корректно отображался русскоязычный текст. 
Решение: 
entity.addPart(pair.getName(), new StringBody(pair.getValue(),
Charset.forName("UTF-8")));
Пояснение: при помощи  StringBody "превращаем" каждую строку (в Java строки Unicode) в UTF - 8. Прежде чем, совершать какие - либо действия с кодировкой, присмотритесь к форме. В моем случае, она выглядит вот так: <form accept-charset='UTF-8' action='/offers' enctype='multipart/form-data' id='offer' method='post'>  

Задача: сделать так, чтобы при отправке обычного post - запроса на сервере корректно отображался русскоязычный текст.
Решение: 
new  UrlEncodedFormEntity(complectPostValuePairs(offer),"UTF-8"));
Пояснение: в качестве параметра для списка из NameValuePair подадим название кодировки.

Задача: сделать гет - запрос.
Пояснение: ничем не отличается от поста, используем HttpGet httpGET. Если нужно куки, юзаем точно тот же способ как и в посте.

Не забываем активно использовать "Проверить элемент" в Google Chrome(нас интересует вкладка "Network"). Просматриваем отданную и переданную информацию.






среда, 9 апреля 2014 г.

Crashlytics как неплохая альтернатива Bugsense

Bugsense - очень и очень неплохой crash reporter для мобильных устройств, существует он довольно давно, и многие мои знакомые пользуются именно им. Не так давно появилась неплохая альтернатива Bugsense, которая еще находится в режиме тестирования, но многие "фишечки" видны уже сейчас. Это crashlytics. Сейчас немного расскажу и покажу как выглядит crashlytics и что это за "фишечки" такие. Во -  первых, crashlytics - это не только вебсайт, но и плагин для eclipse, x-code, android - studio. Плагин этот ставится "Help -> Install new software"(это в eclipse, ничего удивительного, как видите).  Именно в этом и заключается главная фишка - можно видеть баги прямо из вашей среды разработки :) Все очень просто - вы запускаете crashlytics, с помощью кнопки на панели инструментов: 


Затем отмечаете те приложения, за которыми хотите "следить", crashlytics, в свою очередь, добавляет мета - информацию в Manifest - файл вашего Android - проекта:<meta-data android:name="com.crashlytics.ApiKey" android:value="KEY"/> (где KEY - некоторая строка). 
Чтобы все изменения вступили в силу, вам остается только забилдить и запустить ваш проект, а crashlytics подождет:


Всё!!! Теперь вы видите репорты прямо в eclipse! 


Но это еще не все! Как только к вам "пришла" ошибка, вы можете перейти на сайт(который очень крут - фишка номер два :) ), а там просмотреть не только полную информацию о баге, но и:

1) Увидеть красивую статистику (по устройствам, дням, неделям и т.д)
2) Сделать заметку к ошибке
3) Посмотреть состояние всех потоков устройства, на котором был замечен баг
4) Увидеть еще многое и многое :)    


Очень и очень рекомендую.


пятница, 28 марта 2014 г.

Анализируем исходный код 30 apk с помощью PMD

Как — то раз, бродя по просторам интернета, я наткнулся на PMD. PMD служит для поиска «общих недостатков» кода, таких как пустые блоки, создание объекта, который никогда не используется, и так далее. Кроме того, в составе PMD присутствует CPD(copy-paste-detector), который может находить дублирование кода. PMD полностью поддерживает такие языки как Java, JavaScript, XML, XSL, а CPD может работать и с более длинным списком технологий: Java, C, C + +, C #, PHP, Ruby, Fortran, JavaScript.

Стало интересно, а сколько же таких «общих недостатков» можно найти скажем в 30 приложениях Android, которые спокойно лежат себе на 4pda? 


1. Необходимо преобразовать .apk в .jar.

Для этого нужно использовать всем известную dex2jar. Использовать ее очень просто: распаковываем, да пишем примерно следующее: 

./dex2jar.sh имя_вашего_apk.apk.

После процесса преобразования в вашей папке вы найдете файл — «имя_вашего_apk_dex2jar.jar». В этом архиве уже лежат классы, необходимые для последующего анализа, но в не читабельном виде, так сказать. Для того, чтобы мы могли как следует покопаться в нормальных классах, нужно их декомпилировать. 


2. Преобразовываем код из бинарников в читаемый вид.


Из всех проверенных мной преобразователей самым удачным выглядит fernflower. Чтобы воспользоваться им, нужно выполнить команду: 

java -jar fernflower.jar имя_вашего_apk_dex2jar.jar имя_директории

Создастся папка с названием «имя_директории», в которой будет лежать файл с таким же именем, как и исходный. Но в этом случае код можно анализировать.


3. Распаковка архива.


Jar и apk файлы являются архивами, так что их можно запросто распаковать с помощью утилиты unzip.

unzip имя_файла -d имя_директории

4. Натравливаем PMD.


Для анализа исходного кода PMD использует определенный набор правил. Весь список можно глянуть вот тут: правила. Но мы будем использовать всего два набора: android и basic. Для анализа нужно прописать следующую строчку:

./run.sh pmd -d + имя_директории_с_исходниками -f text -R rulesets/java/basic.xml,rulesets/java/android.xml -version 1.7 -language java

В общем — то все, осталось лишь объединить все шаги в единый скрипт и подвести статистику.


5. Пишем простой скрипт на python. 


Сначала обговорим структуру проекта. В корне пусть будут папки: apks(куда мы положим 30 наших произвольных apk'ашек), dex2jar(где будет лежать распакованная одноименная программа), pmd(куда распакуем архив с pmd), наш сприпт. 



import commands
import os
import fnmatch
import matplotlib.pyplot as plt
import collections


abs_path_dir = os.getcwd()
read_jars_folder_name = abs_path_dir + "/apks/read_jars"
text_file_errors = open('all_errors', 'w')


def ensure_dir(f):
    d = os.path.dirname(f)
    if not os.path.exists(d):
        os.makedirs(d)


def covert_to_jar(name_apk):
    os.system(abs_path_dir + "/dex2jar/dex2jar.sh " +
              os.getcwd() + "/apks/" + name_apk)


def conver_to_read_jar(name_file, name_folder):
    full_dir_name = abs_path_dir + "/apks/" + name_file
    path = "java -jar " + abs_path_dir + "/projects/fernflower.jar " + \
        full_dir_name + " " + name_folder
    os.system(path)


def get_all_files_with_pattern(pattern, folder):
    files = []
    for f in os.listdir(folder):
        if fnmatch.fnmatch(f, pattern):
            files.append(f)
    return files


def convert_all_apk_to_jar():
    for f in get_all_files_with_pattern("*.apk", abs_path_dir + "/apks/"):
        covert_to_jar(f)


def convert_all_jar_to_read_jar():
    ensure_dir(read_jars_folder_name)
    print get_all_files_with_pattern("*.jar", abs_path_dir + "/apks/")
    for f in get_all_files_with_pattern("*.jar", abs_path_dir + "/apks/"):
        conver_to_read_jar(f, read_jars_folder_name)


def unpack_all_jars_to_projects():
    files = get_all_files_with_pattern("*.jar", read_jars_folder_name)
    for f in files:
        path = "unzip " + read_jars_folder_name + "/" + \
            f + " -d " + abs_path_dir + "/projects/" + f[:-4]
        os.system(path)


def global_convert_to_projects():
    convert_all_apk_to_jar()
    convert_all_jar_to_read_jar()
    unpack_all_jars_to_projects()
    analysis_all_projects()


def project_analysis(folder):
    path_to_pmd = abs_path_dir + "/pmd/bin/"
    command = path_to_pmd + "run.sh pmd -d " + folder + \
        " -f text -R rulesets/java/basic.xml,rulesets/java/android.xml -version 1.7 -language java"
    status, output = commands.getstatusoutput(command)
    text_file_errors.write(output)


def analysis_all_projects():
    dir_name_proj = abs_path_dir + "/projects/"
    for folder in os.listdir(dir_name_proj):
        project_analysis(dir_name_proj + folder)
    text_file_errors.close()


def get_end_words(x, i):
    answer = ""
    for i in xrange(int(i), len(x)):
        answer += x[int(i)] + " "
    return answer


def delete_small_values(d):
    b = {}
    for k, v in d.iteritems():
        if v > 2:
            b[k] = v
    return b


def get_str_dict(d):
    s = ""
    for k, v in d.iteritems():
        s += str(k) + " - " + str(v) + "\n"
    return s


def draw_graph(d):
    d = delete_small_values(d)
    d = collections.OrderedDict(sorted(d.items()))
    text_statistic = open("statistic.txt", "w")
    text_statistic.write(get_str_dict(d))
    text_statistic.close()
    plt.bar(range(len(d)), d.values(), align='center')
    plt.xticks(range(len(d)), d.keys())
    plt.show()


def get_all_errors():
    strings = open("all_errors", "r+")
    warnings = {}
    for string in strings.readlines():
        x = string.split()
        key = ""
        if len(x[1]) > 2:
            key = get_end_words(x, 1)
        else:
            key = get_end_words(x, 2)

        if key in warnings:
            warnings[key] += 1
        else:
            warnings[key] = 1
    # print warnings
    return warnings


def main():
    global_convert_to_projects()
    text_file_errors.close()
    draw_graph(get_all_errors())


if __name__ == '__main__':
    main()



После работы скрипта, мы получим как статистику в текстовом виде, так и диаграмму(диаграмма строится при помощи самого популярного средства под Python — matplotlib). 


6. Что же все — таки получилось? 


А вышло примерно следующее: 


Avoid empty synchronized blocks — 2099

Avoid instantiating Boolean objects; reference Boolean.TRUE or Boolean.FALSE or call Boolean.valueOf() instead. — 269
Avoid using a branching statement as the last in a loop. — 170
Dont create instances of already existing BigInteger and BigDecimal (ZERO, ONE, TEN) — 10
Empty initializer was found — 41
Empty static initializer was found — 41
Ensure you override both equals() and hashCode() — 149
Invoke equals() on the object you've already ensured is not null — 9
Overriding method merely calls super — 310
The null check here is misplaced; if the variable is null there will be a NullPointerException — 10
These nested if statements could be combined — 22
Unnecessary final modifier in final class — 7501
Useless parentheses. — 3582
An empty statement (semicolon) not part of a loop — 4144
Do not hard code the IP address — 101
Do not hardcode /sdcard. — 11
Do not use if statements that are always true or always false — 21
An operation on an Immutable object (String, BigDecimal or BigInteger) wont change the object itself — 4
super should be called at the end of the method — 72
super should be called at the start of the method — 86

image


Выводы можете делать сами. Мой же вывод такой:

1. PMD достоин того, чтобы использовать его повсеместно, так как таким образом можно избежать не только дублирование кода и создание неиспользуемых переменных, но и выявить более серьезные ошибки. 
2. Прогоняйте ваш код через обфускатор. Желательно всегда.

Ссылки:

PMD: pmd

dex2jar: dex2jar

четверг, 7 ноября 2013 г.

суббота, 10 августа 2013 г.

Немного о ТEX'e и русском языке

Начал я читать книжку по ТЕХ'у и понял, что примеры с русским языком не пашут, уж не знаю почему. Гуглил достаточно долго. Нашел исходник(или как там это в ТЕХ называется?). Все как всегда оказалось просто.

\usepackage[utf8x]{inputenc}
\usepackage[english, russian]{babel}

Система у меня Linux Mint 15. Ищите ответы, и точно найдете :)

пятница, 24 мая 2013 г.

SCALA для нетерпеливых


Хочу поделиться частичкой радости. Вышла и уже пришла книга, которую многие очень долго ждали. В скором времени поделюсь впечатлениями, а может быть и небольшими частичками кода :)

Сервер за 5 баксов или Jetty 6 на Ubuntu

Здесь и сейчас я попытаюсь восстановить полную последовательность действий от взноса денег за сервак на digitalocean, до развертывания полноценного сайта.


1. Регистрируемся на сайте
digitalocean.com. Вносим деньги и выбираем конфигурацию машины. Я выбрал ubuntu 12.04. Сразу же отмечу, что машина имеет несколько оконных менеджеров, но так как у меня была минимальная конфигурация машины, то я решил поставить самый легковесный менеджер - openbox. Поэтому apt-get install openbox.

2. Теперь необходимо поставить jetty. Через apt-get ставится 6 версия(с ней проблем нет). Если ставить более позднюю версию, при деплое war'a начинаются косяки, которые вроде как должны решаться удалением записи "-- demon" в shell-скрипте, отвечающем за запуск  jetty, но нифига что-то не пашет. Поэтому остановимся на шестой версии jetty.


3. Подробнее о деплое. Во-первых, необходимо указать необходимые настройки для jetty. Для этого  подправим файлик  /etc/default/jettyПосле правки он должен выглядеть примерно так:
# change to 0 to allow Jetty to startNO_START=0# change to 'no' or uncomment to use the default setting in /etc/default/rcSVERBOSE=yes# Run Jetty as this user ID (default: jetty)# Set this to an empty string to prevent Jetty from starting automatically#JETTY_USER=jettyJETTY_HOST=$(uname -n)JETTY_HOST=0.0.0.0# The network port used by JettyJETTY_PORT=8999
 Для того, чтобы стартануть jetty нужно написать sudo /etc/init.d/jetty start(если не вводить start, и нажать enter, то выведутся все доступные команды). Во - вторых, для деплоя нужно положить WAR файл в папку /usr/share/jetty/webapps, перезапустить jetty-сервер, а также сделать java -jar start


4. Теперь немного о mySQL server'e. Ставится стандартно apt-get install. 3306 - стандартный порт, через который работает mySQL. При установке записываем пароли :)  


5. После установки mySQL, он пропускает только localhost. Нам необходимо прописать привилегии для ip-адресов, с которых будет вестись работа. Делается это следующими командами:
$ mysql -u root -pEnter password:mysql> use mysqlmysql> GRANT ALL ON *.* to root@'192.168.1.4' IDENTIFIED BY 'your-root-password';mysql> FLUSH PRIVILEGES;
6. А теперь о vnc. Установку сервера производил по мануалу http://www.prolinux.org/node/106.
Собственно для использования необходимо сначала зайти по ssh, запустить vnc4server, а потом спокойненько работать.



четверг, 14 марта 2013 г.

Scons

Scons - это замена makefile. Scons написан на Python. Scons очень удобен и у него замечательная документация. Scons полезен всем :)

Простой пример:
Допустим есть пример, состоящий всего из файлов main.cpp, 1.h, 1.cpp,  2.h,  2.cpp. Необходимо написать простейший сборщик.
Создаем файл SConstruct, почему название именно такое? Потому что оно дается автоматически, как Makefile, только SConstruct :) Затем пишем две строчки внутри файла:
env=Environment()
env.Program('myprog', ['main.cpp',1.cpp','2.cpp'] )

Пишем в консольке scons...
Scons дьявольски умен и сообразителен. И нам и ему понятно, что myprog  и есть наш собранный исполняемый файл.
Main page of scons

Ну а еще можно добавить, что scons используют:





пятница, 25 января 2013 г.

Пример работы с ftplib.

Еще один пост любви к Python. Как передать некоторый список файлов(имеются полные пути) на ftp-сервер? Очень просто :) 


server_ftp = '192.168.0.102' #для примера, в программе можно задать что угодно
login_ftp = "fox100500"
pass_ftp = "fox_134342"
port_ftp = "1069"


def ftp_online():
    ftpConnect = FTP()
    try:
        ftpConnect.connect(server_ftp, port_ftp)
        ftpConnect.login(login_ftp, pass_ftp)
        ftpConnect.quit()
        ftpConnect.close()
        enable = 1
    except:
        enable = 0
    return enable


def copy_file(ftp, path):  # ftp - через что соединяемся, path - абсолютный путь до передаваемого файла
    name_file = path.split('/')[-1:][0]
    send_file = open(path, 'rb')
    ftp.storbinary('STOR ' + name_file, send_file, 1024)


def read_ways_from_file(file_name):
    f = open(file_name, 'r')
    return f.readlines()


def copy_all_files(ftp, lines):  # ftp - через что соединяемся, lines - list абсолютных путей до файлов
    for line in lines:
        line = line.replace('\n', '')
        copy_file(ftp, line)


if __name__ == "__main__":

    if ftp_online() == 1:
        ftpConnect = FTP()
        ftpConnect.connect(server_ftp, port_ftp)
        ftpConnect.login(login_ftp, pass_ftp)
        ftpConnect.cwd('/information')
        st = ftpConnect.pwd()

        copy_all_files(ftpConnect, read_ways_from_file('download.txt'))

Есть некий текстовый файл download.txt, в котором записаны пути до нужных файлов. Нам лишь необходимо запустить скрипт и он скопирует эти файлы на сервак. 

Для чего это может использоваться? Ответ прост - отложенная отправка файлов на android. Есть куча программ, которые превращают андрюшу в сервер, также есть всячиские task менеджеры, которые могут включать и выключать wifi в определенное время, то есть нам лишь необходимо настроить запуск wifi андроида ночью, настроить запуск скрипта - граббера информации(скачка хабра, новостей, запись радио), а потом взять и передать все с помощью данного скриптика. 
Также хочу отметить, что для скачки контента из интерната лучше всего пользоваться wget. Помимо регулировки порядка вложенности скачки страниц он имеет кучу настроек. Допустим, чтобы скачать web-страницу полностью надо ввести в консоль:  wget -k -p <адрес страницы>.