Compare commits

...

259 Commits

Author SHA1 Message Date
root 273e38c5ec TG-293
11 months ago
root f70f7f8e97 Fix: поправлено имя переменной cl_update_binhost_set для cl-builder
1 year ago
root d9a4dc08ec TG-210
1 year ago
root 242863e85a TG-198
1 year ago
root 5efa64ed5a TG-148
1 year ago
root 75a9f4cce9 TG-154
1 year ago
root 4aaab34ac8 TG-148
1 year ago
root 7ed6fc2847 TG-125
1 year ago
root c2c6314a80 TG-125
1 year ago
root 59880c5ce3 исправление
1 year ago
root 54e3fc5881 TG-130
1 year ago
root 4426fbee48 TG-127
1 year ago
root a5b4e5fcc7 TG-127
1 year ago
root a86bd7f1ff TG-127
1 year ago
root f4dc9ce9d4 Merge branch 'master' of https://git.calculate-linux.org/calculate/calculate-utils-3-update
1 year ago
root 945c398bf7 TG-106
1 year ago
Павел Иванов 3eee4d2afd TG-108 fix type
1 year ago
root 052f9c9874 TG-70
1 year ago
root 9b04b1bca6 TG-93
1 year ago
root 48169a8f47 TG-85
1 year ago
root ac4cf20de9 TG-86
1 year ago
root 017fc09c31 TG-80 TG-50
1 year ago
root ee6aa3c018 TG-78
1 year ago
root 9faf04df67 TG-58
1 year ago
root 157c642386 TG-73
1 year ago
root 0aae4a0461 cl-update: fix: other repos sync fix
1 year ago
root 8b275377a5 TG-59
1 year ago
root b8f1789db3 feature: async binhost
1 year ago
root 5137d0f9ff изменено: pym/update/utils/cl_update.py
1 year ago
root 231512dd49 cl-update: remove inner var; fix cache logic for calculating
1 year ago
root c0e82e8a7b Fix: логика выполнения пересена в модуль действий
1 year ago
root 7644f43bd8 изменено: pym/update/update.py
1 year ago
root bbe7d914ce cl-update: Fix: detect_best_binhost logic. Add tmp solution for
1 year ago
root 5e2a3f16ca изменено: pym/update/update.py
1 year ago
root 0bca2fc5cd изменено: pym/update/update.py
1 year ago
root 9aad9102be Fix: cl-update-profile
1 year ago
root c7744229ef Fix: cl-update-profile migrate repo condition
1 year ago
root 61d86149ff calculate-utils: Fix cl-update-profile
1 year ago
root bf6b759bc2 изменено: pym/update/variables/update.py
1 year ago
root 7a01f44877 изменено: pym/update/update.py
1 year ago
root 1ee37f9c43 изменено: pym/update/variables/update.py
1 year ago
root d9f6f0f859 Добавлена обработка логикии версии 3.7.3.0 для cl-builder
1 year ago
Artur 4bb1f3184b изменено: pym/update/update.py
1 year ago
Artur 33c97c66a9 изменено: pym/update/update.py
1 year ago
root 09b21a9740 изменено: pym/update/variables/update.py
1 year ago
root eb9850069d изменено: pym/update/variables/update.py
1 year ago
root a9e5b78c64 изменено: pym/update/update.py
1 year ago
root 7ebe12260f изменено: pym/update/wsdl_update.py
1 year ago
root 061401ca2e изменено: pym/update/update.py
1 year ago
root a03581b798 изменено: pym/update/update.py
1 year ago
root c59f7b06fb изменено: pym/update/update.py
1 year ago
root 78e9deb98a изменено: pym/update/update.py
1 year ago
root 91d05b39e9 изменено: pym/update/update.py
1 year ago
root 9f69807513 изменено: pym/update/update.py
1 year ago
root 2abe2f904b изменено: pym/update/update.py
1 year ago
root f019452b97 изменено: pym/update/update.py
1 year ago
root 413c9155af изменено: pym/update/update.py
1 year ago
root 98bbebd1f6 изменено: pym/update/update.py
1 year ago
Павел Иванов 8534ea0481 Добавлена поддерка нового пути к портежам
2 years ago
Павел Иванов 3d3afa2f3a Удалено условие выбора директории портежей
2 years ago
Павел Иванов 15b833a22a Путь к портежам по умолчанию - /usr/portage
2 years ago
Павел Иванов 4d8f8bf675 Перенос /usr/portage в /var/db/repos/gentoo
2 years ago
idziubenko ec0a21e2c1 added binhost.level check
2 years ago
idziubenko 224ef40ba3 now prints "sync" instead of "update" in sync-only update
2 years ago
idziubenko 75afc8b3a1 added vanilla kernel to module rebuild
2 years ago
idziubenko 9bdb486617 feat: module rebuild now ignores non calculate sources
2 years ago
idziubenko ca364e1dbf feat: added spinner before downloading
2 years ago
idziubenko 7bfae86bcf feat: git: to https:
2 years ago
idziubenko 5535168364 set check for world updated as non-essential
2 years ago
idziubenko a68d23c18f FEAT: added check for non-calculate world update
2 years ago
idziubenko 5deecf793e fixed clear_migration_host
2 years ago
idziubenko 3843da48ef FEAT: added tag saving
2 years ago
idziubenko 94e768f5a9 FIX: fixed migration level increasing on interrupt
2 years ago
idziubenko 006597de02 fixed translation
2 years ago
idziubenko 3c1febf7cb FEAT: added --ignore-level, --force-level,
2 years ago
idziubenko 78033cb110 small translation fix
2 years ago
idziubenko 10ca94e46b fixed localization
2 years ago
idziubenko fa1f25fb93 tooltip encoding fix
2 years ago
idziubenko ccd4e8837d fixed potential error with update level
2 years ago
idziubenko 52b8ade427 updated output grammar
2 years ago
idziubenko 91dd507a21 minor fix to the way binhost_level is chosen
2 years ago
idziubenko 15b6d3b152 fixed builder-update bug
2 years ago
idziubenko 3142073819 fixed scenario when local level is higher than all binhost's
2 years ago
idziubenko 9dfc73b8a1 now if repo's .git is damaged redownloads repo, but doesn't alter folder
2 years ago
idziubenko 0bc12b3320 migration mode is now turned off when --branch is present
2 years ago
idziubenko 489ec1a7a5 fixed cl-builder-update crash when host machine doesn't have a chosen
2 years ago
idziubenko 10b5a0132d fixed builder_update using migration mode
2 years ago
idziubenko 9157e7061d fixed filling in empty update_level in calculate.env
2 years ago
idziubenko d99c45dda3 changed the way decect_best_binhost works with levels
2 years ago
idziubenko 529aa6bc74 added level restoration, general bugfixes
2 years ago
idziubenko 96874fe0ea multiple improvements to migration algo
2 years ago
idziubenko 98c4deae97 multiple improvements to migration algo
3 years ago
idziubenko 9d49a68b38 added output
3 years ago
idziubenko efe2b5bc3c changed behavior of increment_current_level
3 years ago
idziubenko 5fdb31e5c8 removed unneeded import
3 years ago
Иван Дзюбенко 2ff2810339 fixed set level
3 years ago
Иван Дзюбенко 3b79f758eb progress
3 years ago
Иван Дзюбенко 4c1f92d549 added migration host capabilities
3 years ago
idziubenko 1dd1cd46a4 preparation for migration project
3 years ago
idziubenko dbfc0a2110 fixed emerge-like output sometimes crashing
3 years ago
idziubenko 6952049842 pre update for future migration capabilities
3 years ago
idziubenko a2349f2273 ftp to https
3 years ago
idziubenko 7e4d754690 Fixed encoding bugs in update methods
3 years ago
idziubenko e6b5f2e11b fixed hashing
3 years ago
idziubenko 5ebcea46b0 imports to relative
3 years ago
idziubenko 8b356a3d22 fixed/reverted regex
3 years ago
idziubenko 1f2b817d6e list compr, general improvements
3 years ago
idziubenko 0826021939 Merge branch 'master' of git.calculate-linux.org:calculate/calculate-utils-3-update
3 years ago
idziubenko 10922f61c6 Py3 changes
3 years ago
Хирецкий Михаил 5596090d22 Setup fix
3 years ago
idziubenko 659f1d1920 i
3 years ago
Хирецкий Михаил beed790b61 Исправлена проверка обновления
3 years ago
Хирецкий Михаил 2451599fdf Добавлено обновление информации для domain-fastlogin
3 years ago
Хирецкий Михаил fc0315cd7d Исправлен сброс маркера наличия обновлений
3 years ago
Хирецкий Михаил c131228cbc Отключено выполнение обновления если не было изменений в репозиториях или /etc/portage
3 years ago
Хирецкий Михаил 3de843415e Добавлено изменение сборочных зависимостей бинарных пакетов в зависимости от bdeps
4 years ago
Хирецкий Михаил a08838f21d Добавлен параметр --dynamic-deps, при вызове depclean
4 years ago
Хирецкий Михаил 4002685a22 Добавлено создание previous.eix
4 years ago
Хирецкий Михаил 7fd17fd332 Исправлено определение запущенного emerge
4 years ago
Хирецкий Михаил 62dd0d255b Отказ от ветки master64
4 years ago
Хирецкий Михаил e4ed00c052 Использование master64 только для calculate,distros,gentoo
4 years ago
Хирецкий Михаил 9dee71ea21 Переход на ветку master64
4 years ago
Хирецкий Михаил 79bc71f3b6 Исправлена проверка запущенного emerge
4 years ago
Хирецкий Михаил 30e4e3e36d Опция --skip-revdep-rebuild заменена на --revdep-rebuild
4 years ago
Хирецкий Михаил f6bfde8c7e calculate.conf zz-calculate.conf
4 years ago
Хирецкий Михаил 8cdddda360 Переход с Layman на repos.conf
4 years ago
Хирецкий Михаил f204e530e8 Исрпавлено определение пакета текущего загруженного ядра
4 years ago
Хирецкий Михаил 3ece80cac4 Изменено назначение with-bdeps
5 years ago
Хирецкий Михаил 661e8c868e Исправлена ошибка кодировок
5 years ago
Хирецкий Михаил ba91aa0397 Изменёно значение --with-bdeps на y для depclean
5 years ago
Хирецкий Михаил a2243c79d7 Использование параметра with-bdeps только для depclean
5 years ago
Хирецкий Михаил 81b3b9535f Добавлено переименование custom.{short} файлов при переключении профиля
5 years ago
Хирецкий Михаил 84446e44bc Изменена переменная ac_update_sync
6 years ago
Хирецкий Михаил a7bb8b6349 Исравлено удаление устаревших пакетов
6 years ago
Хирецкий Михаил d95aa814d4 Изменён порядок провреки base binhost
6 years ago
Хирецкий Михаил d275ed169a Исправлен двойной вывод сообщения о том, что сервер не найден
6 years ago
Хирецкий Михаил 13cd521646 Исправлена фраза для перевода
6 years ago
Хирецкий Михаил 5e21141676 Обновлены фразы
6 years ago
Хирецкий Михаил 3986bf1b9b Исправлена настройка выбранного зеркала
6 years ago
Хирецкий Михаил 5a20e18d5e Исправлен вызов update_layman
6 years ago
Хирецкий Михаил c28b889044 Исправлено создание директории для временного каталога GPG
6 years ago
root 0dc306541f Переписан проверка подписи Packages
6 years ago
Хирецкий Михаил c859c815f3 Добавлена проверка подписи Packages
6 years ago
Хирецкий Михаил 1614dd4eea Добавлено обновление конфигурационных файлов при сканировании серверов обновлений
6 years ago
Хирецкий Михаил 6f46c24213 Изменены права на закрытые репозитории
6 years ago
Хирецкий Михаил 321f616059 Исправлена ошибка импорта
7 years ago
Хирецкий Михаил 9f916977cf Изменены права на закрытые репозитории
7 years ago
Хирецкий Михаил 9453f0196b Исправлено выполнение шаблонов при переключении профилей
7 years ago
Хирецкий Михаил 78161512fb Добавлена поддержка закрытых репозиториев
7 years ago
Хирецкий Михаил 000b720e7d Исправлено определение with-bdeps
7 years ago
Хирецкий Михаил 1e0187472f Исправлен вызов emerge
7 years ago
Хирецкий Михаил 439b9dd1ee Добавлены переменные для базового бинхоста. Расширены возможности вызова emerge.
7 years ago
Хирецкий Михаил 572ab1de99 Изменена синхронизация репозиториев
7 years ago
Хирецкий Михаил 53669b7f71 Исправлен вывод описания пакетов
7 years ago
Хирецкий Михаил 84e2721236 Изменены права на запуск методов
7 years ago
Хирецкий Михаил 794c480f77 Удален вывод версии в полном названии дистрибутива
7 years ago
Хирецкий Михаил 1168020f6d Вызов depclean при смене параметра --with-bdeps
7 years ago
Хирецкий Михаил cfed8845d5 Исправлены описания переменных
7 years ago
Хирецкий Михаил 20695d8513 Добавлен параметр --with-bdeps для возможности задействовать
7 years ago
Хирецкий Михаил 44a1cd2ec0 Исправлено выполнение команды через CommandExecutor
7 years ago
Хирецкий Михаил 75868e2454 Исправлено вычисление переменных на "сломаных" профилях
7 years ago
Хирецкий Михаил 9084ad5d5c Исправлено выполнение только синхронизации репозиториев
7 years ago
Хирецкий Михаил e3b204f335 Исправлен скрипт-обёртка для синхронизации layman`ом
7 years ago
Хирецкий Михаил 4deee242ee Исправлены Profile переменные
7 years ago
Хирецкий Михаил cc68cd25b5 Исправлена проверка обновления
7 years ago
Хирецкий Михаил 0822bf5fee Добавлена дополнительная проверка необходимости запуска emerge @preserved-rebuild
7 years ago
Хирецкий Михаил c03f98ddd0 Исправлено скачивание Packages.xz для небинарных систем
7 years ago
Хирецкий Михаил 45e124476a Исправлен вызов исправления настроек системы (при использовании --merge-world, rebuild-world)
7 years ago
Хирецкий Михаил 8d38cfe0c0 Изменён порядок репозиториев в сообщение update eix
7 years ago
Хирецкий Михаил d89445dd5c Изменён поряток выполнения emerge -uDN world
7 years ago
Хирецкий Михаил eca0cabfaf Отключено принудительное использование --with-bdeps для вычисления зависимостей
7 years ago
Хирецкий Михаил f60b09185d Исправлна попытка скачивать Packages если binhost пуст
7 years ago
Хирецкий Михаил d26c91369e Добавлено скачивание Packages если он отсутствует или у него не тот TTL
7 years ago
Хирецкий Михаил 2fbafa7f68 Множественные изменения
7 years ago
Хирецкий Михаил 9358b1a4a5 Добавлена возможность изменения номеров установленных пакетов
7 years ago
Хирецкий Михаил 3501cbac2d Добавлена новая задача поиск автозависимостей
7 years ago
Хирецкий Михаил 7ff158264e English update
7 years ago
Хирецкий Михаил 5dab34aa14 English update
7 years ago
Хирецкий Михаил 8a1456c182 Переименована ini.evn/[update] -> [system]
8 years ago
Хирецкий Михаил e832e2348b Изменён порядок репозиториев в сообщение update eix cache
8 years ago
Хирецкий Михаил 193736b850 Исправлен вывод списка обновляемых пакетов
8 years ago
Хирецкий Михаил 13279e6107 Изменён признак определения наличия обновлений: через файл /var/lib
8 years ago
Хирецкий Михаил e7e6acef3c Обновлены иконки
8 years ago
Хирецкий Михаил 6b90d029d7 Удалён вызов python-updater
8 years ago
Хирецкий Михаил d81fc4b781 Исправлена смена профиля
8 years ago
Хирецкий Михаил be07c37b14 Исправлено преключение профиля
8 years ago
Хирецкий Михаил 3594859663 Исправлено вычисление переменной вне действия
8 years ago
Хирецкий Михаил d8e25f5185 Исправлено переключение профилей
8 years ago
Хирецкий Михаил 678735dae2 Скрыта ошибка при синхронизации репозитория, если указанный каталог
8 years ago
Хирецкий Михаил 92af9f18bc Исправлен путь по умолчанию для репозиториев git.calculate.ru -> github.com
8 years ago
Хирецкий Михаил 04b42ba821 Исправлен вывод ошибки о недоступности профиля
8 years ago
Хирецкий Михаил bdbab3c071 Обновлёно отображение названий репозиториев
8 years ago
Хирецкий Михаил 2a28744e41 Исправлено переключение на unstable серверы обновлений
8 years ago
Хирецкий Михаил 0638f9663d Изменён порядок опций
8 years ago
Хирецкий Михаил a7f035d655 Изменён порядок параметров cl-update
8 years ago
Хирецкий Михаил 990c23d454 Исправлена переменная
8 years ago
Хирецкий Михаил a087ecad89 Исправлено использование testing сервера обновлений
8 years ago
Хирецкий Михаил d7dc318e1a Добавлена поддержка использование testing серверов обновлений
8 years ago
Хирецкий Михаил 6188d9c1eb Исправлена синхронизация portage репозитория
8 years ago
Хирецкий Михаил 1039fca5ec Поддержка gentoo названия для /usr/portage
8 years ago
Хирецкий Михаил 97db03579b Update copyrights.
8 years ago
Хирецкий Михаил acaf5b3b76 Исправлена распаковка репозиториев
8 years ago
Хирецкий Михаил 9f988d3bbe Изменено сообщение при eix-update
8 years ago
Хирецкий Михаил b6759a7040 Добавлено логгирование обновления списка бинхостов
8 years ago
Хирецкий Михаил 3adbb14780 Добавлено сообщение если --scan выдал тот же сервер
8 years ago
Хирецкий Михаил 9fc3da1b1f Обновлён вывод сообщения при обновлении eix
8 years ago
Хирецкий Михаил 231d79d8ee Исключён downgrade репозиториев по timestamp
8 years ago
Хирецкий Михаил 72393cf50f Изменены критерии сортировки серверов обновления
8 years ago
Хирецкий Михаил bbcec6fbf0 Обновлены фразы
8 years ago
Хирецкий Михаил a21d0643c9 Улучшена работа с репозиториями
8 years ago
Хирецкий Михаил d0a23b0528 Исправлено вывод версии с которой обновляется пакет
8 years ago
Mike Khiretskiy 4f2809e224 Refactoring
9 years ago
Mike Khiretskiy 20d09bbabf Refactoring
9 years ago
Mike Khiretskiy b3ca8fed9c Рефакторинг
9 years ago
Mike Khiretskiy 66cd9596d7 Refactoring
9 years ago
Mike Khiretskiy 9933d9cdc4 Обработка timeout для urllib.
9 years ago
Mike Khiretskiy 8de913d54a Исправлена работа layman в сборках
9 years ago
Mike Khiretskiy dad8051be9 os.rename заменён на shutil.move
9 years ago
Mike Khiretskiy 23442e2747 Исправлена работа парсера emerge с использованием --ask.
9 years ago
Mike Khiretskiy e3ee99d0c2 Исправлено использование EMERGE_DEFAULT_OPTS
9 years ago
Mike Khiretskiy 3a10dff960 Обработка TemplatesError
9 years ago
Mike Khiretskiy f42e11410b Исправлено обновление репозитория в git
9 years ago
Mike Khiretskiy e43677c569 Обновление иконок
9 years ago
Mike Khiretskiy ef4dee467a Обновление переводов
9 years ago
Mike Khiretskiy c1578da76f Исправлена установка значений переменных
9 years ago
Mike Khiretskiy f445d23e28 Исправлены сообщение и вызов emerge команды
9 years ago
Mike Khiretskiy 3f219d74e1 Переименованы фразы
9 years ago
Mike Khiretskiy 0b8a5e8fe0 Переименованы заголовки таблиц branch
9 years ago
Mike Khiretskiy ee4f335ba9 Переход на константы-ветки
9 years ago
Mike Khiretskiy 301c7759bf Исправление экспертных параметров
9 years ago
Mike Khiretskiy a801ce6dbf Добавлено сообщение при смене binhost, исправлен binhost_list
9 years ago
Mike Khiretskiy eceb9a9f72 cl_builder_binhosts -> cl_builder_binhost_list
9 years ago
Хирецкий Михаил 87d317ac69 Изменён путь до timestamp
9 years ago
Хирецкий Михаил 37aa89c4d2 Смена иконок
9 years ago
Mike Khiretskiy 51637effcf Удалена отладочная информация
9 years ago
Mike Khiretskiy 8c39a51e32 Изенена синхронизация до веток и тэгов
9 years ago
Mike Khiretskiy afdd27ff23 Добавлена поддержка binhost
9 years ago
Mike Khiretskiy abb1c37275 Множественные изменения для использования с calculate-assemble
9 years ago
Mike Khiretskiy f61f898d3c Исправлена работа cl-update в tmux
9 years ago
Mike Khiretskiy 3276ff0a2f Удалён параметр --jobs=1 в emerge parser
9 years ago
Mike Khiretskiy 8b467fc281 Сброс кэша если в списке пакетов есть blocks
9 years ago
Mike Khiretskiy 3bebabda12 Добавлено обновление кэша настраиваемых пакетов после синхронизации
9 years ago
Mike Khiretskiy 6584c7ef30 Исправлено создание отметки необходимости удаления старого ядра
9 years ago
Mike Khiretskiy e15842fe7c Исправлено определение стадии получения бинарных пакетов
9 years ago
Mike Khiretskiy 7008a04a38 Переход на cl-variable
9 years ago
Mike Khiretskiy 246d7861f0 Обновлены переводы
9 years ago
Mike Khiretskiy c0ba8fb019 Добавлена поддержка layman 2.3
9 years ago
Mike Khiretskiy bae480342c Исправлен перевод
9 years ago
Mike Khiretskiy 9a29e24119 При выключенном обновление update.updates не выставляются в on.
9 years ago
Mike Khiretskiy 105c060f6c Исправлена обработка EMERGE_DEFAULT_OPTS
9 years ago
Mike Khiretskiy 9514151562 Сброс статуса наличия обновление ini.env/update.updates при выключении
9 years ago
Mike Khiretskiy 648ff5154e Исправлено вычисление переменной ядра
9 years ago
Mike Khiretskiy f1303ba0a8 Отключена параллельная установка пакетов во время cl-update
9 years ago
Mike Khiretskiy 71c376f8b6 Добавлен параметр пропуска revdep-rebuild
9 years ago
Mike Khiretskiy 3c144a611e Исправлена зависимость setup.py от модуля calculate-lib
9 years ago
Mike Khiretskiy 2ffa285517 Пропуск eix-sync и прочих действий основанных на значении cl_update_other_set
9 years ago
Mike Khiretskiy 15160e92a4 Исправлен вывод при удалении пакета
9 years ago
Mike Khiretskiy 7841fcdbf2 Исправлено определение бинарных пакетов в выводе emerge
10 years ago
Mike Khiretskiy 64d3bd36f1 Обновление переводов
10 years ago

6
.gitignore vendored

@ -0,0 +1,6 @@
revert_changes_to_vmachine
push_to_vmachine*
.vscode
*.pyc
*.pyo
*.bak

@ -1,7 +1,7 @@
#!/bin/bash
# если выполняется обновление уже полученного репозитория
if [[ $1 == "pull" ]]
if [[ $1 == "pull" ]] || [[ $1 == "remote" ]]
then
# получить название репозитория
if [[ -f profiles/repo_name ]]
@ -16,13 +16,15 @@ then
fi
# получить список репозиториев дистрибутива
native_reps=,$(/usr/sbin/cl-core --method core_variables_show \
--only-value update.cl_update_rep_name),
native_reps=,$(/usr/libexec/calculate/cl-variable --value update.cl_update_rep_name),
# если обновляемый репозиторий от дистрибутива
if echo $native_reps | grep -q ,${repo_name},
then
# отбновить репозиторий через утилиты Calculate
/usr/sbin/cl-core --method update --rep $repo_name --sync-only on --skip-eix-update -T none
if [[ $1 == "pull" ]]
then
# отбновить репозиторий через утилиты Calculate
/usr/sbin/cl-core --method update --rep $repo_name --sync-only on --skip-eix-update -T none
fi
else
# выполнить обновление через git
/usr/bin/git $*

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# Copyright 2012-2013 Calculate Ltd. http://www.calculate-linux.org
# Copyright 2012-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -14,14 +14,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__app__ = 'calculate-update'
__version__ = '3.1.8'
__version__ = '3.4.1'
import os
import sys
from calculate.lib.datavars import DataVars
from calculate.lib.cl_lang import setLocalTranslate
setLocalTranslate('cl_update3',sys.modules[__name__])
setLocalTranslate('cl_update3', sys.modules[__name__])
class DataVarsUpdate(DataVars):
"""Variable class for desktop package"""
@ -31,4 +31,3 @@ class DataVarsUpdate(DataVars):
self.importVariables()
self.importVariables('calculate.update.variables')
self.defaultModule = "update"

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# Copyright 2014 Calculate Ltd. http://www.calculate-linux.org
# Copyright 2015-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -13,6 +13,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import hashlib
import os
@ -22,13 +23,16 @@ import re
import sys
from calculate.lib.utils.colortext.palette import TextState
from calculate.lib.utils.tools import ignore
from calculate.lib.utils.portage import EmergePackage, PackageList, EmergeUpdateInfo, \
EmergeRemoveInfo, Git, GitError
from calculate.lib.utils.git import Git, GitError
from calculate.lib.utils.portage import (EmergePackage, PackageList,
EmergeUpdateInfo,
EmergeRemoveInfo)
Colors = TextState.Colors
import pexpect
from calculate.lib.utils.files import getProgPath, readLinesFile, listDirectory, \
writeFile, readFile
from calculate.lib.utils.files import (getProgPath, readLinesFile,
listDirectory,
writeFile, readFile)
from calculate.lib.utils.colortext.output import XmlOutput
from calculate.lib.utils.colortext.converter import (ConsoleCodes256Converter,
XmlConverter)
@ -39,6 +43,7 @@ from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate, _
setLocalTranslate('cl_update3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)
linux_term_env = {'TERM': 'linux'}
class EmergeError(Exception):
"""
@ -50,7 +55,7 @@ class EmergeNeedRootError(EmergeError):
pass
class CommandExecutor(object):
class CommandExecutor():
"""
Запуск программы для объекта Emerge
"""
@ -59,27 +64,39 @@ class CommandExecutor(object):
def __init__(self, cmd, params, env=None, cwd=None, logfile=None):
self.cwd = cwd
self.env = env or dict(os.environ)
self.env.update({'EINFO_QUIET':'NO'})
self.env.update({'EINFO_QUIET': 'NO'})
self.env.update(linux_term_env)
self.cmd = cmd
self.params = params
self.child = None
if logfile:
self.logfile = logfile
def get_command(self):
return [self.cmd] + list(self.params)
#TODO make sure pexpect encoding doesn't mess up anything
#if it does, instead put log file as binary maybe?
def execute(self):
if self.child is None:
self.child = pexpect.spawn(self.cmd,
command_data = self.get_command()
self.child = pexpect.spawn(command_data[0], command_data[1:],
logfile=open(self.logfile, 'w'),
env=self.env, cwd=self.cwd, timeout=None)
env=self.env, cwd=self.cwd, timeout=None,
encoding="UTF-8", codec_errors='ignore')
return self.child
def close(self):
if self.child is not None:
self.child.close()
try:
self.child.close()
except pexpect.ExceptionPexpect:
self.child.close(force=True)
self.child = None
def success(self):
if self.child:
self.child.read()
if self.child.isalive():
self.child.wait()
return self.child.exitstatus == 0
@ -99,51 +116,98 @@ class EmergeCommand(CommandExecutor):
"""
# параметры по умолчанию
default_params = ["-av", "--color=y", "--nospinner"]
cmd = getProgPath("/usr/bin/emerge")
emerge_cmd = getProgPath("/usr/bin/emerge")
def __init__(self, packages, extra_params=None, env=None, cwd=None,
logfile=None, emerge_default_opts=None):
logfile=None, emerge_default_opts=None, env_update=None,
use=""):
extra_params = extra_params or []
self.child = None
self.packages = packages
self.params = self.default_params + extra_params
wrong_default_opts = ("--columns","--ask ", "--ask=")
if emerge_default_opts is None:
default_env = {'CLEAN_DELAY': '0'}
else:
default_env = {
'CLEAN_DELAY': '0',
'EMERGE_DEFAULT_OPTS': " ".join(filter(
lambda x: not any(y in x for y in wrong_default_opts),
emerge_default_opts))
}
default_env.update(os.environ)
self.env = env or default_env
self.cwd = cwd
if logfile:
self.logfile = logfile
if env is None:
if emerge_default_opts is None:
env = {'CLEAN_DELAY': '0'}
else:
env = {
'CLEAN_DELAY': '0',
'EMERGE_DEFAULT_OPTS': re.sub(
r'(?:^|\s)(--columns)(?=\s|$)', '',
emerge_default_opts)
}
if use:
env["USE"] = use
env.update(os.environ)
if env_update is not None:
env.update(env_update)
def execute(self):
if self.child is None:
self.child = pexpect.spawn(self.cmd, self.params + self.packages,
logfile=open(self.logfile, 'w'),
env=self.env, cwd=self.cwd, timeout=None)
return self.child
params = self.default_params + extra_params + packages
super().__init__(self.emerge_cmd, params=params,
env=env, cwd=cwd, logfile=logfile)
class EmergeInformationBlock(object):
_color_block = "(?:\033\[[^m]+?m)?"
_new_line = "\r*\n"
def Chroot(chroot_path, obj):
"""
Преобразовать команду (экземпляр объекта) в chroot
:param obj: экземпляр команды
:param chroot_path: путь для chroot
:return:
"""
old_get_command = obj.get_command
def get_command():
chrootCmd = '/usr/bin/chroot'
bashCmd = '/bin/bash'
bash_command = (
"env-update &>/dev/null;"
"source /etc/profile &>/dev/null;"
"{cmd}".format(cmd=" ".join(old_get_command())))
return [chrootCmd, chroot_path, bashCmd, "-c", bash_command]
obj.get_command = get_command
return obj
def Linux32(obj):
"""
Преобразовать команду (экземпляр объекта) в вызов под linux32
:param obj: экземпляр команды
:return:
"""
old_get_command = obj.get_command
def get_command():
return ["/usr/bin/linux32"] + old_get_command()
obj.get_command = get_command
return obj
class InfoBlockInterface():
"""
Интерфейс для информационного блока
"""
action = None
token = None
result = None
text_converter = ConsoleCodes256Converter(XmlOutput())
def get_block(self, child):
pass
def add_element(self, element):
"""
:type element: InfoBlockInterface
"""
pass
class EmergeInformationBlock(InfoBlockInterface):
_color_block = "(?:\033\[[^m]+?m)?"
_new_line = "(?:\r*\n)"
end_token = ["\n"]
re_block = None
action = None
re_match_type = type(re.match("", ""))
re_type = type(re.compile(""))
def __init__(self, parent):
"""
:type parent: EmergeParser
:type parent: InfoBlockInterface
"""
self.result = None
self.text_converter = parent.text_converter
@ -152,6 +216,9 @@ class EmergeInformationBlock(object):
self.children = []
def add_element(self, element):
"""
:type element: InfoBlockInterface
"""
self.children.append(element)
def __str__(self):
@ -209,18 +276,22 @@ class InstallPackagesBlock(EmergeInformationBlock):
"""
list = PackageList([])
remove_list = PackageList([])
block_packages = False
_new_line = EmergeInformationBlock._new_line
_color_block = EmergeInformationBlock._color_block
token = "\n["
end_token = ["\r\n\r", "\n\n"]
re_block = re.compile(r"((?:^\[.*?{nl})+)".format(nl=_new_line),
re.MULTILINE)
re_blocks = re.compile(r"\[{c}blocks{c} {c}b".format(c=_color_block))
def get_data(self, match):
super(InstallPackagesBlock, self).get_data(match)
super().get_data(match)
list_block = XmlConverter().transform(self.result).split('\n')
self.list = PackageList(map(EmergeUpdateInfo, list_block))
self.remove_list = PackageList(map(EmergeRemoveInfo, list_block))
self.block_packages = any(self.re_blocks.search(x) for x in list_block)
class UninstallPackagesBlock(EmergeInformationBlock):
@ -232,7 +303,7 @@ class UninstallPackagesBlock(EmergeInformationBlock):
_new_line = EmergeInformationBlock._new_line
_color_block = EmergeInformationBlock._color_block
token = ["Calculating removal order",
"These are the packages that would be unmerged",]
"These are the packages that would be unmerged"]
end_token = re.compile("All selected packages:.*\n")
re_block = re.compile(
r"(?:{token}).*?{nl}(.*){nl}All selected packages: (.*?){nl}".
@ -251,7 +322,29 @@ class UninstallPackagesBlock(EmergeInformationBlock):
self.list = PackageList(map(EmergePackage, list_block))
class FinishEmergeGroup(EmergeInformationBlock):
class GroupEmergeInformationBlock(EmergeInformationBlock):
"""
Группа блоков
"""
def get_block(self, child):
self.children_get_block(child)
self.result = True
def children_get_block(self, child):
for block in self.children:
block.get_block(child)
def children_action(self, child):
for block in (x for x in self.children if x.result and x.action):
if block.action(child) is False:
return False
def action(self, child):
self.children_action(child)
return False
class FinishEmergeGroup(GroupEmergeInformationBlock):
"""
Блок завершения команды
"""
@ -274,19 +367,6 @@ class FinishEmergeGroup(EmergeInformationBlock):
else:
self.result = True
def children_get_block(self, child):
for block in self.children:
block.get_block(child)
def children_action(self, child):
for block in (x for x in self.children if x.result and x.action):
if block.action(child) is False:
break
def action(self, child):
self.children_action(child)
return False
class PrepareErrorBlock(EmergeInformationBlock):
"""
@ -335,21 +415,88 @@ class DownloadSizeBlock(EmergeInformationBlock):
else:
return "0 kB"
class SkippedPackagesBlock(EmergeInformationBlock):
"""
Размер скачиваемых обновлений
"""
token = "The following update has been skipped"
end_token = ["For more information, see the MASKED"]
re_block = re.compile(
r"(The following update has.*?)(?=For more information)", re.S)
class QuestionBlock(EmergeInformationBlock):
def __str__(self):
if self.result:
return self.result
else:
return ""
class QuestionGroup(GroupEmergeInformationBlock):
"""
Блок вопроса
Группа блоков разбора вопросов от emerge
"""
default_answer = "yes"
_color_block = EmergeInformationBlock._color_block
token = "Would you like"
end_token = ["]", "\n"]
_color_block = EmergeInformationBlock._color_block
re_block = re.compile(
"(Would you.*)\[{c}Yes{c}/{c}No{c}".format(c=_color_block))
def get_block(self, child):
try:
before = child.before
token = child.match
if type(self.end_token) == self.re_type:
child.expect(self.end_token)
match = child.match.group()
else:
child.expect_exact(self.end_token)
match = child.match
data = token + child.before + match
child.before = before
for block in self.children:
child.match = re.search(block.token, data)
block.get_block(child)
if block.result:
break
except pexpect.EOF:
child.buffer = "".join(
[x for x in (child.before, child.after, child.buffer)
if type(x) == str])
class QuestionChangeConfigBlock(GroupEmergeInformationBlock):
"""
Вопрос об изменении конфигурационных файлов
"""
token = "Would you like to add these changes to your config files"
def get_block(self, child):
if child.match:
self.result = self.token
self.children_get_block(child)
def action(self, child):
if self.result:
child.send("no\n")
if child.isalive():
child.wait()
self.children_action(child)
class QuestionBlock(EmergeInformationBlock):
"""
Блок вопроса
"""
default_answer = "yes"
token = "Would you"
def get_block(self, child):
if child.match:
self.result = self.token
def action(self, child):
if self.result:
child.send("%s\n" % self.default_answer)
return False
class NeedRootBlock(EmergeInformationBlock):
@ -370,7 +517,7 @@ class NotifierInformationBlock(EmergeInformationBlock):
Информационный блок поддерживающий observing
"""
def __init__(self, parent):
super(NotifierInformationBlock, self).__init__(parent)
super().__init__(parent)
self.observers = []
def get_data(self, match):
@ -379,6 +526,13 @@ class NotifierInformationBlock(EmergeInformationBlock):
def add_observer(self, f):
self.observers.append(f)
def clear_observers(self):
self.observers = []
def remove_observer(self, f):
if f in self.observers:
self.observers.remove(f)
def notify(self, observer, groups):
observer(groups)
@ -427,11 +581,12 @@ class FetchingTarball(NotifierInformationBlock):
Происходит скачивание архивов
"""
token = "Saving to:"
re_block = re.compile("Saving to:\s*(\S+)?")
re_block = re.compile("Saving to:\s*['](\S+)?[']")
def notify(self, observer, groups):
observer(groups[0])
class InstallingPackage(NotifierInformationBlock):
"""
Запуск устанавливаемого пакета
@ -447,7 +602,8 @@ class InstallingPackage(NotifierInformationBlock):
"of {c}(\d+){c}\) {c}([^\s\033]+){c}".format(c=_color_block))
def notify(self, observer, groups):
binary = bool(self.binary and groups[2] in self.binary)
strpkg = str(EmergePackage(groups[2]))
binary = bool(self.binary and strpkg in self.binary)
observer(EmergePackage(groups[2]), binary=binary)
def mark_binary(self, package):
@ -507,23 +663,27 @@ class RevdepPercentBlock(NotifierInformationBlock):
except pexpect.EOF:
self.result = ""
class EmergeParser(object):
class EmergeParser(InfoBlockInterface):
"""
Парсер вывода emerge
"""
def __init__(self, command, run=False):
self.text_converter = ConsoleCodes256Converter(XmlOutput())
self.command = command
self.elements = {}
self.install_packages = InstallPackagesBlock(self)
self.uninstall_packages = UninstallPackagesBlock(self)
self.question = QuestionBlock(self)
self.question_group = QuestionGroup(self)
self.change_config_question = QuestionChangeConfigBlock(
self.question_group)
self.question = QuestionBlock(self.question_group)
self.finish_block = FinishEmergeGroup(self)
self.need_root = NeedRootBlock(self)
self.prepare_error = PrepareErrorBlock(self.finish_block)
self.change_config_question.add_element(self.prepare_error)
self.download_size = DownloadSizeBlock(self)
self.skipped_packages = SkippedPackagesBlock(self)
self.emerging_error = EmergeingErrorBlock(self)
self.installing = InstallingPackage(self)
@ -544,6 +704,9 @@ class EmergeParser(object):
self.fetching.action = lambda child: None
def add_element(self, element):
"""
:type element: InfoBlockInterface
"""
if element.token:
if type(element.token) == list:
for token in element.token:
@ -559,7 +722,7 @@ class EmergeParser(object):
while True:
index = child.expect_exact(self.elements.keys())
element = self.elements.values()[index]
element = list(self.elements.values())[index]
element.get_block(child)
if element.action:
if element.action(child) is False:
@ -575,7 +738,7 @@ class EmergeParser(object):
self.close()
class MtimeCheckvalue(object):
class MtimeCheckvalue():
def __init__(self, *fname):
self.fname = fname
@ -597,30 +760,31 @@ class MtimeCheckvalue(object):
class Md5Checkvalue(MtimeCheckvalue):
def value_func(self, fn):
return hashlib.md5(readFile(fn)).hexdigest()
return hashlib.md5(readFile(fn, binary=True)).hexdigest()
class GitCheckvalue(object):
def __init__(self, rpath):
class GitCheckvalue():
def __init__(self, git, rpath):
self.rpath = rpath
self.git = Git()
self.git = git
def checkvalues(self):
with ignore(GitError):
if self.git.is_git(self.rpath):
yield self.rpath, Git().getCurrentCommit(self.rpath)
yield self.rpath, self.git.getCurrentCommit(self.rpath)
class EmergeCache(object):
class EmergeCache():
"""
Кэш пакетов
"""
cache_file = '/var/lib/calculate/calculate-update/world.cache'
# список файлов проверяемый по mtime на изменения
check_list = [MtimeCheckvalue('/etc/make.conf',
'/etc/portage',
'/etc/make.profile'),
'/etc/portage',
'/etc/make.profile'),
Md5Checkvalue('/var/lib/portage/world',
'/var/lib/portage/world_sets')]
'/var/lib/portage/world_sets')]
logger = log("emerge-cache",
filename="/var/log/calculate/emerge-cache.log",
formatter="%(asctime)s - %(levelname)s - %(message)s")
@ -638,16 +802,16 @@ class EmergeCache(object):
f.write("{fn}={val}\n".format(fn=fn, val=val))
f.write('\n')
for pkg in package_list:
f.write("%s\n"% str(pkg))
self.logger.info("Setting cache (%d packages)"%len(package_list))
f.write("%s\n" % str(pkg))
self.logger.info("Setting cache (%d packages)" % len(package_list))
def drop_cache(self, reason=None):
if path.exists(self.cache_file):
with ignore(OSError):
os.unlink(self.cache_file)
self.logger.info("Droping cache. Reason: %s"%reason)
self.logger.info("Droping cache. Reason: %s" % reason)
else:
self.logger.info("Droping empty cache. Reason: %s"%reason)
self.logger.info("Droping empty cache. Reason: %s" % reason)
def get_cached_package_list(self):
self.read_cache()
@ -667,10 +831,10 @@ class EmergeCache(object):
return True
else:
reason = "Unknown"
for k,v in self.get_control_values().items():
for k, v in self.get_control_values().items():
if k in self.files_control_values:
if v != self.files_control_values[k]:
reason = "%s was modified"%k
reason = "%s was modified" % k
else:
reason = "Checksum of file %s is not exist" % k
self.logger.info("Failed to get cache. Reason: %s" % reason)
@ -680,7 +844,7 @@ class EmergeCache(object):
self.files_control_values = {}
cache_file_lines = readLinesFile(self.cache_file)
for line in cache_file_lines:
if not "=" in line:
if "=" not in line:
break
k, v = line.split('=')
self.files_control_values[k] = v.strip()
@ -689,6 +853,7 @@ class EmergeCache(object):
def get_control_values(self):
def generate():
for obj in self.check_list:
for checkvalue in obj.checkvalues():
yield checkvalue
for check_value in obj.checkvalues():
yield check_value
return dict(generate())

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# Copyright 2014 Calculate Ltd. http://www.calculate-linux.org
# Copyright 2014-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -13,37 +13,51 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import os
from os import path
import shutil
import os
from calculate.lib.utils.files import listDirectory, readFile, readLinesFile, \
makeDirectory, removeDir
from calculate.lib.utils.portage import Git
from update import UpdateError
from calculate.lib.utils.files import (listDirectory, readFile, readLinesFile,
makeDirectory, removeDir)
from calculate.lib.utils.git import Git
from .update import UpdateError
from calculate.lib.cl_lang import setLocalTranslate, _
setLocalTranslate('cl_update3', sys.modules[__name__])
DEFAULT_BRANCH = "master"
DEFAULT_BRANCH = Git.Reference.Master
class RepositoryStorageInterface():
def __iter__(self):
raise StopIteration
def get_profiles(self, url, branch=DEFAULT_BRANCH):
return []
def get_repository(self, url, branch=DEFAULT_BRANCH):
return None
class RepositoryStorage(object):
class RepositoryStorage(RepositoryStorageInterface):
directory = '/tmp'
def __init__(self, directory):
def __init__(self, git, directory):
self.directory = directory
self.git = git
makeDirectory(directory)
def __iter__(self):
git = Git()
for dn in listDirectory(self.directory, onlyDir=True, fullPath=True):
if git.is_git(dn):
yield ProfileRepository(path.basename(dn), self)
if self.git.is_git(dn):
yield ProfileRepository(self.git, path.basename(dn), self)
def get_profiles(self, url, branch=DEFAULT_BRANCH):
return []
def get_repository(self, url, branch=DEFAULT_BRANCH):
return None
class ProfileStorage(RepositoryStorage):
def get_profiles(self, url, branch=DEFAULT_BRANCH):
rep = self.get_repository(url, branch)
@ -54,6 +68,7 @@ class ProfileStorage(RepositoryStorage):
def get_repository(self, url, branch=DEFAULT_BRANCH):
return None
class LocalStorage(ProfileStorage):
"""
Локальное хранилище репозиториев, при запросе по урлу смотрит, доступные
@ -64,7 +79,6 @@ class LocalStorage(ProfileStorage):
if rep.is_like(url, branch):
return rep
class CacheStorage(ProfileStorage):
"""
Хранилище репозиториев, при запросе по урлу смотрит, доступные
@ -76,15 +90,15 @@ class CacheStorage(ProfileStorage):
if rep.is_like(url, branch):
return rep
else:
return ProfileRepository.clone(url, self, branch)
return ProfileRepository.clone(self.git, url, self,
branch or DEFAULT_BRANCH)
class RepositoryStorageSet(RepositoryStorage):
class RepositoryStorageSet(RepositoryStorageInterface):
"""
Набор хранилищ репозиториев
"""
def __init__(self):
self.storages = [LocalStorage('/var/lib/layman'),
CacheStorage('/var/calculate/tmp/update')]
def __init__(self, *storages):
self.storages = storages
def get_profiles(self, url, branch=DEFAULT_BRANCH):
"""
@ -108,6 +122,10 @@ class RepositoryStorageSet(RepositoryStorage):
for rep in self:
if rep.is_like(url, branch):
return rep
for storage in self.storages:
rep = storage.get_repository(url, branch)
if rep:
return rep
return None
def is_local(self, url, branch=DEFAULT_BRANCH):
@ -123,7 +141,7 @@ class RepositoryStorageSet(RepositoryStorage):
def __repr__(self):
return "Repository set"
class Profile(object):
class Profile():
"""
Профиль репозитория
"""
@ -133,14 +151,22 @@ class Profile(object):
self.repository = repository
self.profile = profile
self.arch = arch
self._path = None
@property
def path(self):
if self._path:
return self._path
return path.join(self.repository.directory,"profiles", self.profile)
@path.setter
def path(self, value):
self._path = value
return value
@classmethod
def from_string(cls, repository, s):
parts = filter(None, s.split())
parts = [x for x in s.split() if x]
if len(parts) == 3 and parts[0] in cls.available_arch:
return Profile(repository, parts[1], parts[0])
return None
@ -152,13 +178,14 @@ class Profile(object):
self.repository.directory)
class ProfileRepository(object):
class ProfileRepository():
"""
Репозиторий либо скачивается, либо берется из кэша
"""
def __init__(self, name, storage):
def __init__(self, git, name, storage):
self._storage = storage
self.name = name
self.git = git
@property
def storage(self):
@ -180,22 +207,28 @@ class ProfileRepository(object):
str(e))
@classmethod
def clone(cls, url, storage, branch=DEFAULT_BRANCH):
def clone(cls, git, url, storage, branch=DEFAULT_BRANCH):
name = path.basename(url)
if name.endswith(".git"):
name = name[:-4]
git = Git()
rpath = path.join(storage.directory, name)
if path.exists(rpath):
removeDir(rpath)
if git.is_private_url(url):
try:
makeDirectory(rpath)
os.chmod(rpath, 0o700)
except OSError:
pass
git.cloneRepository(url, rpath, branch)
pr = cls(name, storage)
pr = cls(git, name, storage)
repo_name = pr.repo_name
if name != repo_name:
rpath_new = path.join(storage.directory, repo_name)
if not path.exists(rpath_new):
os.rename(rpath, rpath_new)
pr = cls(repo_name, storage)
if path.exists(rpath_new):
removeDir(rpath_new)
shutil.move(rpath, rpath_new)
pr = cls(git, repo_name, storage)
return pr
@property
@ -217,30 +250,32 @@ class ProfileRepository(object):
@property
def url(self):
git = Git()
return git.get_url(self.directory, "origin")
return self.git.get_url(self.directory, "origin")
@property
def branch(self):
git = Git()
return git.getBranch(self.directory)
return self.git.getBranch(self.directory)
def sync(self):
"""
Синхронизировать репозиторий
"""
git = Git()
if not git.pullRepository(self.directory, quiet_error=True):
git.resetRepository(self.directory, to_origin=True)
git.pullRepository(self.directory, quiet_error=True)
if not self.git.pullRepository(self.directory, quiet_error=True):
self.git.resetRepository(self.directory, to_origin=True)
self.git.pullRepository(self.directory, quiet_error=True)
def get_profiles(self):
"""
Получить список профилей репозитория
"""
profiles_desc = path.join(self.directory, "profiles/profiles.desc")
return filter(None, (Profile.from_string(self, line)
for line in readLinesFile(profiles_desc)))
return list(filter(None, (Profile.from_string(self, line)
for line in readLinesFile(profiles_desc))))
@staticmethod
def get_local_profiles(directory):
profiles_desc = path.join(directory, "profiles/profiles.desc")
return list(filter(None, (Profile.from_string(Profile, line)
for line in readLinesFile(profiles_desc))))
def __repr__(self):
return "<ProfileRepository %s url=%s>" % (self.directory, self.url)

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2012-2013 Calculate Ltd. http://www.calculate-linux.org
# Copyright 2012-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -16,21 +16,18 @@
import os
from os import path
from itertools import ifilter
from calculate.core.datavars import DataVarsCore
from calculate.core.server.gen_pid import search_worked_process
from calculate.lib.cl_template import SystemIni
from calculate.lib.utils.content import getCfgFiles
from calculate.lib.utils.files import getRunCommands, readFile
import time
from calculate.lib.utils.files import getRunCommands, readFile, writeFile
class UpdateInfo(object):
class UpdateInfo():
"""
Информационный объект о процессе обновления
"""
section = "update"
varname = "updates"
update_file = "/var/lib/calculate/calculate-update/updates.available"
def __init__(self, dv=None):
if dv is None:
@ -40,22 +37,27 @@ class UpdateInfo(object):
self.dv = dv
def need_update(self):
return self.update_ready() or self.check_for_dispatch()
return self.update_ready()
@classmethod
def update_ready(cls):
"""
Проверить есть ли обновления по ini.env
"""
return SystemIni().getVar(cls.section, cls.varname) == u'on'
return path.exists(cls.update_file)
@classmethod
def set_update_ready(cls, value):
"""
Установить статус обновления
"""
SystemIni().setVar(cls.section,
{cls.varname:"on" if value else "off"})
try:
if value:
writeFile(cls.update_file).close()
else:
os.unlink(cls.update_file)
except OSError:
pass
def check_for_dispatch(self):
"""
@ -68,7 +70,7 @@ class UpdateInfo(object):
"""
Проверить есть ли уже запущенная копия console-gui
"""
return any(ifilter(lambda x: "cl-console-gui" in x, getRunCommands()))
return any([x for x in getRunCommands() if "cl-console-gui" in x])
def update_already_run(self):
"""
@ -92,10 +94,12 @@ class UpdateInfo(object):
@outdated_kernel.setter
def outdated_kernel(self, value):
flag_path = self.dv.Get('update.cl_update_outdated_kernel_path')
if value:
with open(flag_path, 'w') as f:
f.write(self.dv.Get('update.cl_update_kernel_version'))
else:
if path.exists(flag_path):
os.unlink(flag_path)
try:
if value:
with writeFile(flag_path) as f:
f.write(self.dv.Get('update.cl_update_kernel_version'))
else:
if path.exists(flag_path):
os.unlink(flag_path)
except IOError:
pass

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright 2015-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class EmergeMark:
Schedule = "schedule"
Premerge = "check updates"
PythonUpdater = "update python modules"
PerlCleaner = "update perl modules"
KernelModules = "update kernel modules"
Depclean = "depclean"
XorgModules = "update xorg modules"
PreservedLibs = "update preserved libs"
RevdepRebuild = "revdep rebuild"
Prelink = "prelink"
Automagic = "check for auto depends"
SaveTag = "save latest calculate update tag"

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# Copyright 2010-2013 Calculate Ltd. http://www.calculate-linux.org
# Copyright 2010-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -15,12 +15,13 @@
# limitations under the License.
import sys
from calculate.core.server.func import Action, Tasks
from calculate.core.server.func import Action
from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
from calculate.lib.utils.files import FilesError
from calculate.update.update import UpdateError
from calculate.lib.utils.portage import GitError
from ..update import UpdateError
from calculate.lib.utils.git import GitError
_ = lambda x: x
setLocalTranslate('cl_update3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)
@ -36,12 +37,11 @@ class ClSetupUpdateAction(Action):
failedMessage = __("Failed to configure the updates autocheck procedure!")
interruptMessage = __("Configuration manually interrupted")
# список задач для дейсвия
# список задач для действия
tasks = [
{'name': 'set_variables',
'method': 'Update.setAutocheckParams(cl_update_autocheck_set,'
'cl_update_autocheck_interval,'
'cl_update_other_set,'
'cl_update_cleanpkg_set)'}
]
'cl_update_autocheck_interval,'
'cl_update_other_set,'
'cl_update_cleanpkg_set)'}
]

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# Copyright 2010-2013 Calculate Ltd. http://www.calculate-linux.org
# Copyright 2010-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -15,113 +15,363 @@
# limitations under the License.
import sys
from calculate.core.server.func import Action, Tasks
from calculate.core.server.func import Action, Tasks, AllTasks
from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
from calculate.lib.utils.colortext import get_color_print, Colors
from calculate.lib.utils.files import FilesError
from calculate.update.update import UpdateError
from calculate.update.emerge_parser import EmergeError
from calculate.lib.utils.portage import GitError, Eix, EmergeLog, \
EmergeLogNamedTask, PackageList
from calculate.lib.cl_template import TemplatesError
from calculate.lib.utils.binhosts import BinhostError
from calculate.lib.utils.files import FilesError, readFile
from ..update import UpdateError
from ..emerge_parser import EmergeError
from calculate.lib.utils.git import GitError
from calculate.lib.utils.portage import (EmergeLog, isPkgInstalled,
EmergeLogNamedTask, PackageList)
from ..update_tasks import EmergeMark
_ = lambda x: x
setLocalTranslate('cl_update3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)
class ClUpdateAction(Action):
"""
Действие обновление конфигурационных файлов
"""
# ошибки, которые отображаются без подробностей
native_error = (FilesError, UpdateError, GitError, EmergeError)
successMessage = None
failedMessage = None
interruptMessage = __("Update manually interrupted")
def get_synchronization_tasks(object_name):
Object = lambda s: "%s.%s"%(object_name, s)
return [
{'name': 'reps_synchronization',
'group': __("Repositories synchronization"),
'tasks': [
# создать объект проверки PGP
{'name': 'prepare_gpg',
'method': Object("prepare_gpg()"),
},
# создать объект хранилище серверов обновлений
{'name': 'create_binhost_data',
'method': Object('create_binhost_data()')
},
{
'name': 'perform_migration_system_pre_check',
'method': Object('perform_migration_system_pre_check()'),
'condition': lambda Get, GetBool: (Get('cl_update_ignore_level') == 'off')
},
# проверить валиден ли текущий хост
{'name': 'check_current_binhost',
'message': __("Checking current binhost"),
'essential': False,
'method': Object('check_current_binhost(update.cl_update_binhost)'),
'condition': lambda GetBool, Get: (
not GetBool('update.cl_update_binhost_recheck_set') and
not Get('update.cl_update_binhost_choice') and
Get('update.cl_update_binhost_choice') != 'auto' and
Get('update.cl_update_sync_rep') and
Get('update.cl_update_binhost') and
Get('update.cl_update_use_migration_host') == 'off')
},
{'name': 'drop_packeges_cache',
'method': 'Update.drop_packages_cache()',
'depend': AllTasks.failed_all("check_current_binhost")
},
{'name': 'not_use_search:failed_base_binhost',
'error': __("Failed to use base binhost"),
'method': Object("delete_binhost()"),
'depend': AllTasks.failed_all("check_current_binhost")
},
{'name': 'check_chosen_binhost',
'message': _("Checking chosen binhost"),
'method': Object('check_chosen_binhost(update.cl_update_binhost_choice)'),
'essential': False,
'condition': lambda GetBool, Get: (
not GetBool('update.cl_update_binhost_recheck_set') and
Get('update.cl_update_binhost_choice') and
Get('update.cl_update_binhost_choice') != 'auto'),
'depend': AllTasks.hasnot("check_current_binhost")},
{'name': 'failed_check_chosen_binhost',
'method': Object('check_if_migration()'),
'depend': AllTasks.failed_all("check_chosen_binhost")},
{'name': 'group_find_binhost',
'group': '',
'while': (~AllTasks.has_any("detect_best_binhost") &
((AllTasks.failed_all("update_packages_cache")
& ~AllTasks.has_any("not_use_search")) |
~AllTasks.has_any("sync_reps"))) & Tasks.success(),
'condition': lambda GetBool, Get: (GetBool('update.cl_update_usetag_set') and
Get('update.cl_update_sync_rep')),
'tasks': [
# найти лучший сервер обновлений
{'name': 'detect_best_binhost',
'method': Object(f'detect_best_binhost({object_name.lower()}.cl_update_ignore_level)'),
'essential': False,
'depend': (Tasks.success() & ~AllTasks.has_any("not_use_search") &
(AllTasks.failed_one_of("check_current_binhost") |
AllTasks.failed_one_of("check_chosen_binhost") |
(AllTasks.hasnot("check_current_binhost") &
AllTasks.hasnot("check_chosen_binhost")) |
AllTasks.success_all("sync_reps"))),
'condition': lambda Get: Get('update.cl_update_use_migration_host') == 'off',
},
{'name': 'interrupt_on_no_leveled_binhost',
'method': Object("interrupt_on_no_leveled_binhost()"),
'essential': True,
'depend': (Tasks.success() & ~AllTasks.has_any("not_use_search") &
(~AllTasks.success_one_of("check_current_binhost")) &
AllTasks.failed_all("detect_best_binhost")),
'condition': lambda Get: Get('update.cl_update_use_migration_host') == 'off'
},
{'name': 'set_migration_binhost',
'method': Object('set_migration_host()'),
'depend': (Tasks.success() & ~AllTasks.has_any("not_use_search")|
AllTasks.success_all("sync_reps")),
'condition': lambda Get: Get('update.cl_update_use_migration_host') == 'on'
},
# запасная синхронизация, в ходе которой ветки обновляются до
# master
# {'name': 'sync_reps_fallback',
# 'foreach': 'update.cl_update_sync_rep',
# 'message':
# __("Fallback syncing the {eachvar:capitalize} repository"),
# 'method': Object('syncRepositories(eachvar,True)'),
# 'depend': Tasks.success() & AllTasks.failed_one_of("detect_best_binhost"),
# },
# # обновление переменных информации из binhost
# {'name': 'sync_reps_fallback:update_binhost_list',
# 'method': Object('update_binhost_list()'),
# 'depend': Tasks.success() & AllTasks.failed_one_of("detect_best_binhost"),
# },
# # найти лучший сервер обновлений
# {'name': 'sync_reps_fallback:detect_best_binhost',
# 'method': Object('detect_best_binhost()'),
# 'depend': Tasks.success() & AllTasks.failed_one_of("detect_best_binhost"),
# },
{'name': 'sync_reps',
'foreach': 'update.cl_update_sync_rep',
'message': __("Checking {eachvar:capitalize} updates"),
'method': Object('syncRepositories(eachvar)'),
'condition': lambda Get: Get('update.cl_update_sync_rep'),
'depend': Tasks.success() & ~AllTasks.success_all("update_packages_cache")
},
{'name': 'sync_reps:update_local_info_binhost',
'method': Object('update_local_info_binhost()'),
'condition': lambda Get: Get("update.cl_update_binhost_choice") == 'auto'
or not Get("update.cl_update_binhost_choice")
or Get("update.cl_update_binhost_recheck_set"),
'depend': AllTasks.hasnot('check_chosen_binhost')
},
{'name': 'sync_reps:update_binhost_list',
'essential': False,
'method': Object('update_binhost_list()'),
'condition': lambda GetBool: GetBool('update.cl_update_outdate_set')
},
{'name': 'sync_reps:update_packages_cache',
'message': __("Update packages index"),
'method': Object('download_packages(update.cl_update_portage_binhost,'
'update.cl_update_package_cache, update.cl_update_package_cache_sign,'
'update.cl_update_gpg)'),
'condition': lambda Get, GetBool: (
Get('update.cl_update_package_cache') and (
Get('update.cl_update_outdate_set') == 'on' or
Get('update.cl_update_package_cache_set') == 'on'))
},
],
},
{'name': 'no_server',
'error': __("Failed to find the binary updates server"),
'method': Object("delete_binhost()"),
# method: который должен удалить текущую информацию о сервере обновлений
'depend': (~Tasks.has_any("failed_base_binhost") & (Tasks.failed() |
Tasks.success() & AllTasks.failed_one_of("update_packages_cache"))),
'condition': lambda GetBool, Get: (GetBool('update.cl_update_usetag_set') and
Get('update.cl_update_sync_rep')),
},
{'name': 'sync_reps',
'foreach': 'update.cl_update_sync_rep',
'message': __("Checking {eachvar:capitalize} updates"),
'method': Object('syncRepositories(eachvar)'),
'condition': lambda Get, GetBool: (Get('update.cl_update_sync_rep') and
not GetBool('update.cl_update_usetag_set')),
},
{'name': 'update_rep_list',
'message': __("Repository cache update"),
'method': Object('update_rep_list()'),
'condition': lambda Get: (isPkgInstalled(
"app-eselect/eselect-repository", prefix=Get('cl_chroot_path')) and
Get('cl_chroot_path') != "/"),
'essential': False,
},
{'name': 'sync_other_reps',
'foreach': 'update.cl_update_other_rep_name',
'message': __("Syncing the {eachvar:capitalize} repository"),
'method': Object('syncOtherRepository(eachvar)'),
'condition': lambda GetBool: (GetBool('update.cl_update_other_set') or
not GetBool('update.cl_update_other_git_exists'))
},
{'name': 'trim_reps',
'foreach': 'update.cl_update_sync_rep',
'message': __("Cleaning the history of the "
"{eachvar:capitalize} repository"),
'method': Object('trimRepositories(eachvar)'),
'condition': lambda Get: (Get('update.cl_update_sync_rep') and
Get('update.cl_update_onedepth_set') == 'on')
},
{'name': 'sync_reps:regen_cache',
'foreach': 'update.cl_update_sync_overlay_rep',
'essential': False,
'method': Object('regenCache(eachvar)'),
'condition': (
lambda Get: (Get('update.cl_update_outdate_set') == 'on' and
Get('update.cl_update_egencache_force') != 'skip' or
Get('update.cl_update_egencache_force') == 'force'))
},
{'name': 'sync_other_reps:regen_other_cache',
'foreach': 'update.cl_update_other_rep_name',
'method': Object('regenCache(eachvar)'),
'essential': False,
},
{'name': 'eix_update',
'message': __("Updating the eix cache for "
"{update.cl_update_eix_repositories}"),
'method': Object('eixUpdate(cl_repository_name)'),
'condition': (
lambda Get: (Get('update.cl_update_outdate_set') == 'on' and
Get('update.cl_update_eixupdate_force') != 'skip' or
Get('update.cl_update_eixupdate_force') == 'force'))
},
{'name': 'update_setup_cache',
'message': __("Updating the cache of configurable packages"),
'method': Object('updateSetupCache()'),
'essential': False,
'condition': lambda Get: Get('update.cl_update_outdate_set') == 'on'
},
{'name': 'sync_reps:cleanpkg',
'message': __("Removing obsolete distfiles and binary packages"),
'method': Object('cleanpkg()'),
'condition': (
lambda Get: Get('update.cl_update_cleanpkg_set') == 'on' and
Get('update.cl_update_outdate_set') == 'on'),
'essential': False
},
# сообщение удачного завершения при обновлении репозиториев
{'name': 'success_syncrep',
'message': __("Synchronization finished"),
'depend': (Tasks.success() & Tasks.has_any("sync_reps",
"sync_other_reps",
"emerge_metadata",
"eix_update")),
}
]
},
]
class UpdateConditions():
@staticmethod
def was_installed(pkg, task_name):
def func():
task = EmergeLog(EmergeLogNamedTask(task_name))
return bool(PackageList(task.list)[pkg])
return func
@staticmethod
def need_depclean(pkg, task_name):
def func(Get):
task = EmergeLog(EmergeLogNamedTask(task_name))
return (bool(PackageList(task.list)[pkg])
or Get('cl_update_outdated_kernel_set') == 'on')
or Get('cl_update_force_depclean_set') == 'on'
or Get('cl_update_outdated_kernel_set') == 'on'
or Get('cl_update_world_hash_set') == 'on')
return func
def need_upgrade(pkg):
@staticmethod
def force_preserved(Get):
pfile = "/var/lib/portage/preserved_libs_registry"
content = readFile(pfile).strip()
if not content or content[1:-1].strip() == '':
return False
else:
return True
@staticmethod
def check_world_updated_after_tag_save():
def func():
return bool(Eix(pkg, Eix.Option.Upgrade).get_packages())
task = EmergeLog(EmergeLogNamedTask(EmergeMark.SaveTag))
return task.did_update_world_happen()
return func
def pkg_color(text):
return text
class ClUpdateAction(Action):
"""
Действие обновление конфигурационных файлов
"""
# ошибки, которые отображаются без подробностей
native_error = (FilesError, UpdateError,
TemplatesError, BinhostError,
GitError, EmergeError)
log_names = {'schedule': "schedule",
'premerge': "check updates",
'python_updater': "update python modules",
'perl_cleaner': "update perl modules",
'kernel_modules': "update kernel modules",
'depclean': "depclean",
'xorg_modules': "update xorg modules",
'preserved_libs': "update preserved libs",
'revdep': "revdep rebuild"}
successMessage = None
failedMessage = None
interruptMessage = __("Update manually interrupted")
emerge_tasks = [
{'name': 'save_bdeps_val',
'method': 'Update.save_with_bdeps()',
'essential': False
},
{'name': 'update_fastlogin_domain',
'method': "Update.update_fastlogin_domain_path()"
},
{'name': 'drop_portage_hash_on_sync',
'method': 'Update.drop_portage_state_hash()',
'condition': lambda Get: Get('cl_update_outdate_set') == 'on'
},
{'name': 'premerge_group',
'group': __("Checking for updates"),
'tasks': [
{'name': 'premerge',
'message': __("Calculating dependencies"),
'method': 'Update.premerge("-uDN","--with-bdeps=y","@world")',
'condition': lambda Get:Get('cl_update_sync_only_set') == 'off'
'method': 'Update.premerge("-uDN","@world")',
'condition': lambda Get: (
Get('cl_update_sync_only_set') == 'off' and
Get('cl_update_pretend_set') == 'on') and \
(Get('cl_update_world') != "update" or
Get('cl_update_outdate_set') == 'on' or
Get('cl_update_settings_changes_set') == 'on' or
Get('cl_update_binhost_recheck_set') == 'on' or
Get('cl_update_force_fix_set') == 'on' or
Get('update.cl_update_package_cache_set') == 'on')
}],
},
{'name': 'premerge:update',
'condition': lambda Get:Get('cl_update_pretend_set') == 'off',
'depend': Tasks.result("premerge", eq='yes')
},
},
{'name': 'update',
'condition': lambda Get:Get('cl_update_pretend_set') == 'off' and \
(Get('cl_update_world') != "update" or
Get('cl_update_outdate_set') == 'on' or
Get('cl_update_settings_changes_set') == 'on' or
Get('cl_update_binhost_recheck_set') == 'on' or
Get('cl_update_force_fix_set') == 'on' or
Get('cl_update_available_set') == 'on' or
Get('update.cl_update_package_cache_set') == 'on')
},
{'name': 'update_other',
'condition': lambda Get: ( Get('cl_update_pretend_set') == 'off' and
Get('cl_update_sync_only_set') == 'off')
},
},
{'name': 'update:update_world',
'group': __("Updating packages"),
'tasks': [
{'name': 'update:update_world',
{'name': 'update_world',
'message': __("Calculating dependencies"),
'method': 'Update.emerge("-uDN","--with-bdeps=y","@world")',
}
]
},
{'name': 'update:update_python',
'group': __("Updating Python"),
'method': 'Update.emerge_ask(cl_update_pretend_set,'
'"-uDN","@world")',
}
],
'condition': lambda Get: (Get('cl_update_sync_only_set') == 'off' and
(Get('update.cl_update_outdate_set') == 'on' or
Get('cl_update_settings_changes_set') == 'on'))
},
{'name': 'update_other:update_perl',
'group': __("Updating Perl modules"),
'tasks': [
{'name': 'update:python_updater',
'message': __('Find & rebuild packages broken due '
'to a Python upgrade'),
'method': 'Update.emergelike("python-updater")',
'condition': was_installed('dev-lang/python$',
log_names['python_updater']),
'decoration': 'Update.update_task("%s")' % log_names[
'python_updater']
},
]
},
{'name': 'update:update_perl',
'group': __("Updating Perl"),
'tasks': [
{'name': 'update:perl_cleaner',
{'name': 'update_other:perl_cleaner',
'message': __('Find & rebuild packages and Perl header files '
'broken due to a perl upgrade'),
'method': 'Update.emergelike("perl-cleaner", "all")',
'condition': was_installed('dev-lang/perl$',
log_names['perl_cleaner']),
'decoration': 'Update.update_task("%s")' % log_names[
'perl_cleaner']
'condition': UpdateConditions.was_installed(
'dev-lang/perl$', EmergeMark.PerlCleaner),
'decoration': 'Update.update_task("%s")' % EmergeMark.PerlCleaner
},
]
},
@ -131,8 +381,9 @@ class ClUpdateAction(Action):
{'name': 'update_other:update_depclean',
'message': __("Calculating dependencies"),
'method': 'Update.depclean()',
'condition': need_depclean('.*', log_names['depclean']),
'decoration': 'Update.update_task("%s")' % log_names['depclean']
'condition': UpdateConditions.need_depclean(
'.*', EmergeMark.Depclean),
'decoration': 'Update.update_task("%s")' % EmergeMark.Depclean
},
]
},
@ -141,47 +392,55 @@ class ClUpdateAction(Action):
'tasks': [
{'name': 'update_other:module_rebuild',
'message': __('Updating Kernel modules'),
'method': 'Update.emerge("@module-rebuild")',
'condition': was_installed('sys-kernel/.*source',
log_names['kernel_modules']),
'decoration': 'Update.update_task("%s")' % log_names[
'kernel_modules']
'method': 'Update.emerge("","@module-rebuild")',
'condition': UpdateConditions.was_installed(
'sys-kernel/(calculate-sources|gentoo-kernel|vanilla-kernel)', EmergeMark.KernelModules),
'decoration': 'Update.update_task("%s")' %
EmergeMark.KernelModules
},
{'name': 'update_other:x11_module_rebuild',
'message': __('Updating X.Org server modules'),
'method': 'Update.emerge("@x11-module-rebuild")',
'condition': was_installed('x11-base/xorg-server',
log_names['xorg_modules']),
'decoration': 'Update.update_task("%s")' % log_names[
'xorg_modules']
'method': 'Update.emerge("","@x11-module-rebuild")',
'condition': UpdateConditions.was_installed(
'x11-base/xorg-server', EmergeMark.XorgModules),
'decoration': 'Update.update_task("%s")' %
EmergeMark.XorgModules
},
{'name': 'update_other:preserved_rebuild',
'message': __('Updating preserved libraries'),
'method': 'Update.emerge("@preserved-rebuild")',
'condition': was_installed('.*', log_names['preserved_libs']),
'decoration': 'Update.update_task("%s")' % log_names[
'preserved_libs']
},
'method': 'Update.emerge("","@preserved-rebuild")',
'condition': lambda Get: (UpdateConditions.was_installed(
'.*', EmergeMark.PreservedLibs)() or
UpdateConditions.force_preserved(Get)),
'decoration': 'Update.update_task("%s")' %
EmergeMark.PreservedLibs
},
{'name': 'update_other:revdev_rebuild',
'message': __('Checking reverse dependencies'),
'method': 'Update.revdep_rebuild("revdep-rebuild")',
'condition': was_installed('.*', log_names['revdep']),
'decoration': 'Update.update_task("%s")' % log_names['revdep']
'condition': lambda Get: (Get(
'cl_update_revdep_rebuild_set') == 'on' and
UpdateConditions.was_installed(
'.*', EmergeMark.RevdepRebuild)()),
'decoration': 'Update.update_task("%s")' %
EmergeMark.RevdepRebuild
},
{'name': 'update_other:dispatch_conf_end',
'message': __("Updating configuration files"),
'method':'Update.dispatchConf()',
'method': 'Update.dispatchConf()',
'condition': lambda Get: (Get('cl_dispatch_conf') != 'skip' and
Get('cl_update_pretend_set') == 'off')
},
]
},
{'name': 'update:set_upto_date_cache',
'method': 'Update.setUpToDateCache()'
{'name': 'set_upto_date_cache',
'method': 'Update.setUpToDateCache()',
'condition': lambda Get: Get('cl_update_sync_only_set') == 'off' and \
Get('cl_update_pretend_set') == 'off'
}
]
# список задач для дейсвия
# список задач для действия
tasks = [
{'name': 'check_schedule',
'method': 'Update.checkSchedule(cl_update_autocheck_interval,'
@ -192,103 +451,107 @@ class ClUpdateAction(Action):
{'name': 'check_run',
'method': 'Update.checkRun(cl_update_wait_another_set)'
},
{'name': 'reps_synchronization',
'group': __("Repositories synchronization"),
'tasks': [
{'name': 'sync_reps',
'foreach': 'cl_update_sync_rep',
'message': __("Syncing the {eachvar:capitalize} repository"),
'method': 'Update.syncRepositories(eachvar)',
'condition': lambda Get: Get('cl_update_sync_rep')
},
{'name': 'sync_other_reps',
'foreach': 'cl_update_other_rep_name',
'message': __("Syncing the {eachvar:capitalize} repository"),
'method': 'Update.syncLaymanRepository(eachvar)',
'condition': lambda Get: Get('cl_update_other_set') == 'on'
},
{'name': 'sync_reps:regen_cache',
'foreach': 'cl_update_sync_overlay_rep',
'essential': False,
'method': 'Update.regenCache(eachvar)',
'condition': (
lambda Get: (Get('cl_update_outdate_set') == 'on' and
Get('cl_update_egencache_force') != 'skip' or
Get('cl_update_egencache_force') == 'force'))
},
{'name': 'sync_other_reps:regen_other_cache',
'foreach': 'cl_update_other_rep_name',
'method': 'Update.regenCache(eachvar)',
'essential': False,
},
{'name': 'emerge_metadata',
'message': __("Metadata transfer"),
'method': 'Update.emergeMetadata()',
'condition': (
lambda Get: (Get('cl_update_outdate_set') == 'on' and
Get('cl_update_metadata_force') != 'skip' or
Get('cl_update_metadata_force') == 'force'))
},
{'name': 'eix_update',
'message': __("Updating the eix cache"),
'method': 'Update.eixUpdate()',
'condition': (
lambda Get: (Get('cl_update_outdate_set') == 'on' and
Get('cl_update_eixupdate_force') != 'skip' or
Get('cl_update_eixupdate_force') == 'force'))
},
{'name': 'sync_reps:cleanpkg',
'message': __("Removing obsolete distfiles and binary packages"),
'method': 'Update.cleanpkg()',
'condition': (lambda Get: Get('cl_update_cleanpkg_set') == 'on' and
Get('cl_update_outdate_set') == 'on'),
'essential': False
},
# сообщение удачного завершения при обновлении репозиториев
{'name': 'success_syncrep',
'message': __("Synchronization finished"),
'depend': (Tasks.success() & Tasks.has_any("sync_reps",
"sync_other_reps",
"emerge_metadata",
"eix_update")),
}
]
{'name': 'check_if_world_was_updated',
'method': 'Update.update_set_current_saved_tag()',
'essential': False,
'condition': lambda Get: (Get('cl_update_sync_only_set') == 'off' and \
Get('cl_update_pretend_set') == 'off' and \
UpdateConditions.check_world_updated_after_tag_save()())
},
{'name': 'reps_synchronization',
] + get_synchronization_tasks("Update") + [
{'name': 'system_configuration',
'group': __("System configuration"),
'tasks': [
{'name': 'binhost_changed',
'method': 'Update.message_binhost_changed()'
},
{'name': 'revision',
'message': __("Fixing the settings"),
'method': 'Update.applyTemplates(install.cl_source,'
'cl_template_clt_set,True,None,False)',
'condition': lambda Get: Get('cl_templates_locate')
},
'condition': lambda Get, GetBool: (Get('cl_templates_locate') and
(Get('cl_update_world') != "update" or
GetBool('cl_update_outdate_set') or
GetBool('cl_update_binhost_recheck_set') or
GetBool('cl_update_force_fix_set') or
GetBool('update.cl_update_package_cache_set')))
},
{'name': 'dispatch_conf',
'message': __("Updating configuration files"),
'method':'Update.dispatchConf()',
'condition': lambda Get: (Get('cl_dispatch_conf') != 'skip' and
Get('cl_update_pretend_set') == 'off')
},
'method': 'Update.dispatchConf()',
'condition': lambda Get, GetBool: (Get('cl_dispatch_conf') != 'skip' and
Get('cl_update_pretend_set') == 'off' and
(GetBool('cl_update_outdate_set') or
GetBool('cl_update_binhost_recheck_set') or
GetBool('cl_update_force_fix_set') or
GetBool('update.cl_update_package_cache_set')))
},
]
}
}
] + emerge_tasks + [
{'name':'failed',
'error':__("Update failed"),
{'name': 'failed',
'error': __("Update failed"),
'depend': (Tasks.failed() & Tasks.hasnot("interrupt") &
(Tasks.hasnot("check_schedule") |
Tasks.success_all("check_schedule")))},
{'name': 'failed',
'depend': Tasks.failed_all("check_schedule")
},
},
# сообщение удачного завершения при обновлении ревизии
{'name': 'drop_portage_hash',
'method': 'Update.drop_portage_state_hash()',
'depend': Tasks.failed()
},
{'name': 'drop_world_hash',
'method': 'Update.drop_world_state_hash()',
'depend': Tasks.failed()},
{'name': 'update:set_current_level',
'method': 'Update.update_increment_current_level()',
'depend': (Tasks.success() & Tasks.hasnot("interrupt") &
Tasks.success_all("update") & Tasks.hasnot("check_schedule")),
'condition': lambda Get: Get('cl_update_sync_only_set') == 'off' and
Get('cl_update_pretend_set') == 'off' and
Get('update.cl_update_use_migration_host') == 'on'
},
{'name': 'update_world:set_latest_tag',
'method': 'Update.update_set_current_saved_tag()',
'depend': (Tasks.success() & Tasks.hasnot("interrupt") &
Tasks.success_all("update") & Tasks.hasnot("check_schedule")),
'condition': lambda Get: Get('cl_update_sync_only_set') == 'off' and
Get('cl_update_pretend_set') == 'off',
'decoration': 'Update.update_task("%s")' % EmergeMark.SaveTag
},
{'name': 'update:save_portage_hash',
'method': 'Update.save_portage_state_hash()',
'condition': lambda Get: Get('cl_update_sync_only_set') == 'off'
},
{'name': 'update:save_world_hash',
'method': 'Update.save_world_state_hash()',
'condition': lambda Get: Get('cl_update_sync_only_set') == 'off'
},
{'name': 'clear_migration_host',
'method': 'Update.delete_binhost()',
'depend': (Tasks.hasnot("check_schedule")),
'condition': lambda Get: Get('update.cl_update_use_migration_host') == 'on'
},
# сообщение удачного завершения при обновлении ревизии
{'name': 'success_rev',
'message': __("System update finished!"),
'condition': lambda Get: (Get('cl_update_rev_set') == 'on' and
Get('cl_update_pretend_set') == 'off')
},
Get('cl_update_pretend_set') == 'off' and
Get('cl_update_sync_only_set') == 'off')
},
# сообщение удачного завершения при обновлении ревизии
{'name': 'success_sync_only',
'message': __("System sync finished!"),
'condition': lambda Get: (Get('cl_update_rev_set') == 'on' and
Get('cl_update_pretend_set') == 'off' and
Get('cl_update_sync_only_set') == 'on')
},
# сообщение удачного завершения при пересоздании world
{'name': 'success_world',
'message': __("World rebuild finished!"),
'condition': lambda Get: Get('cl_rebuild_world_set') == 'on'
},
},
]

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# Copyright 2010-2013 Calculate Ltd. http://www.calculate-linux.org
# Copyright 2010-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,10 +17,13 @@
import sys
from calculate.core.server.func import Action, Tasks
from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
from calculate.lib.cl_template import TemplatesError
from calculate.lib.utils.binhosts import BinhostError
from calculate.lib.utils.files import FilesError
from calculate.update.update import UpdateError
from calculate.lib.utils.portage import GitError
from ..update import UpdateError
from calculate.lib.utils.git import GitError
_ = lambda x: x
setLocalTranslate('cl_update3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)
@ -30,96 +33,76 @@ class ClUpdateProfileAction(Action):
Действие обновление конфигурационных файлов
"""
# ошибки, которые отображаются без подробностей
native_error = (FilesError, UpdateError, GitError)
native_error = (FilesError,
TemplatesError, BinhostError,
UpdateError, GitError)
successMessage = __("The profile was successfully updated")
failedMessage = __("Failed to update the profile")
interruptMessage = __("Profile update manually interrupted")
# список задач для дейсвия
# список задач для действия
tasks = [
{'name': 'migrate_repository',
'method': 'Update.migrateCacheRepository('
'cl_update_profile_rep,cl_update_profile_branch)',
'message': __("Repository transfer"),
'condition': lambda Get: not (
Get('cl_update_profile_storage').is_local(
Get('cl_update_profile_rep'),
Get('cl_update_profile_branch')))
},
{'name': 'reconfigure_vars',
'method': 'Update.reconfigureProfileVars()'
},
{'name': 'reps_synchronization',
'group': __("Repositories synchronization"),
'cl_update_profile_url,cl_update_profile_branch,'
'cl_update_profile_storage)',
'condition': lambda Get: Get('cl_update_profile_url')},
{'name': 'profile_from_url',
'group': __('setting up from url'),
'tasks': [
{'name': 'sync_reps',
'foreach': 'cl_update_profile_sync_rep',
'message': __("Syncing the {eachvar:capitalize} repository"),
'method': 'Update.syncRepositories(eachvar)',
#'condition': lambda Get: Get('cl_update_profile_sync_rep')
{'message': __("Repository transfer"),
'condition': lambda Get: not (
Get('cl_update_profile_storage').is_local(
Get('cl_update_profile_url'),
Get('cl_update_profile_branch'))) and Get('cl_update_profile_check_sync_allowed'),
},
{'name': 'sync_reps:regen_cache',
'foreach': 'cl_update_sync_overlay_rep',
'message': __("Updating the {eachvar:capitalize} repository cache"),
'essential': False,
'method': 'Update.regenCache(eachvar)',
'condition': (
lambda Get: (Get('cl_update_outdate_set') == 'on' and
Get('cl_update_metadata_force') != 'skip' or
Get('cl_update_metadata_force') == 'force'))
{'name': 'reconfigure_vars1',
'method': 'Update.invalidateVariables("cl_update_profile_storage")',
},
{'name': 'emerge_metadata',
'message': __("Metadata transfer"),
'method': 'Update.emergeMetadata()',
'condition': (
lambda Get: (Get('cl_update_outdate_set') == 'on' and
Get('cl_update_metadata_force') != 'skip' or
Get('cl_update_metadata_force') == 'force'))
{'name': 'check_datavars',
'error': _("Profile not found in master"),
'condition': lambda Get: not Get('update.cl_update_profile_datavars'),
},
{'name': 'eix_update',
'message': __("Updating the eix cache"),
'method': 'Update.eixUpdate()',
'condition': (
lambda Get: (Get('cl_update_outdate_set') == 'on' and
Get('cl_update_eixupdate_force') != 'skip' or
Get('cl_update_eixupdate_force') == 'force'))
{'name': 'drop_binhosts',
'method': 'Update.drop_binhosts(update.cl_update_profile_datavars)',
},
# сообщение удачного завершения при обновлении репозиториев
{'name': 'success_syncrep',
'message': __("Synchronization finished"),
'depend': (Tasks.success() & Tasks.has_any("sync_reps",
"sync_other_reps",
"emerge_metadata",
"eix_update")),
}
]
},
{'name': 'reconfigure_vars',
'method': 'Update.reconfigureProfileVars(cl_update_profile_datavars,'
'cl_chroot_path)',
},
],
'depend': Tasks.has('migrate_repository')
},
{'name': 'reps_synchronization',
'group': __("Setting up the profile"),
'tasks': [
{'name': 'set_profile',
'message': __("Switching to profile {cl_update_profile_system}"),
'method': 'Update.setProfile()'
},
{'name': 'revision',
'message': __("Fixing the settings"),
'method': 'Update.applyTemplates(install.cl_source,'
'cl_template_clt_set,True,None,False)',
'condition': lambda Get: Get('cl_templates_locate')
},
{'name': 'reconfigure',
'message': __("The system is being configured"),
'method': 'Update.applyProfileTemplates(cl_template_clt_set,True,False)',
'condition': lambda Get: (Get('cl_update_templates_locate')
and Get('cl_update_skip_setup_set') == 'off')
},
{'name': 'dispatch_conf',
'message': __("Updating configuration files"),
'method':'Update.dispatchConf()',
'condition': lambda Get: Get('cl_dispatch_conf') != 'skip'
},
{'name': 'set_profile',
'message': __("Switching to profile {cl_update_profile_system}"),
'method': 'Update.setProfile(cl_update_profile_system)'
},
{'name': 'rename_custom',
'method': 'Update.rename_custom_files()',
},
{'name': 'revision',
'message': __("Fixing the settings"),
'method': 'Update.applyProfileTemplates(cl_template_clt_set,'
'True,False,"update_profile")',
'condition': lambda Get: Get('cl_templates_locate')
},
{'name': 'reconfigure',
'message': __("The system is being configured"),
'method': 'Update.applyProfileTemplates(cl_template_clt_set,'
'True,False,"merge")',
'condition': lambda Get: (
Get('cl_update_templates_locate') and
Get('cl_update_skip_setup_set') == 'off')
},
{'name': 'dispatch_conf',
'message': __("Updating configuration files"),
'method': 'Update.dispatchConf()',
'condition': lambda Get: Get('cl_dispatch_conf') != 'skip'
},
]
}
}
]

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
# Copyright 2008-2013 Calculate Ltd. http://www.calculate-linux.org
# Copyright 2008-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import action
import update
from . import action
from . import update
section = "update"

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# Copyright 2008-2013 Calculate Ltd. http://www.calculate-linux.org
# Copyright 2013-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -14,10 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
from os import path
from calculate.lib.datavars import Variable,VariableError,ReadonlyVariable
from calculate.lib.datavars import ReadonlyVariable
from calculate.lib.cl_lang import setLocalTranslate
setLocalTranslate('cl_update3',sys.modules[__name__])
@ -29,6 +27,7 @@ class VariableAcUpdateSync(ReadonlyVariable):
"""
def get(self):
action = self.Get("cl_action")
if action in ("sync", 'update_profile'):
if action in ("sync", 'update_profile') and \
not self.Get('cl_merge_pkg'):
return "on"
return "off"

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# Copyright 2010-2013 Calculate Ltd. http://www.calculate-linux.org
# Copyright 2010-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -16,20 +16,22 @@
import sys
from calculate.lib.datavars import VariableError,DataVarsError,DataVars
from calculate.lib.datavars import VariableError,DataVarsError
from calculate.core.server.func import WsdlBase
from calculate.install.install import InstallError
from calculate.update.update import Update,UpdateError
from calculate.lib.utils.portage import GitError
from utils.cl_update import ClUpdateAction
from utils.cl_update_profile import ClUpdateProfileAction
from utils.cl_setup_update import ClSetupUpdateAction
from calculate.lib.cl_lang import setLocalTranslate,getLazyLocalTranslate
setLocalTranslate('cl_update3',sys.modules[__name__])
from .update import Update, UpdateError
from calculate.lib.utils.git import GitError
from .utils.cl_update import ClUpdateAction
from .utils.cl_update_profile import ClUpdateProfileAction
from .utils.cl_setup_update import ClSetupUpdateAction
from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate, _
setLocalTranslate('cl_update3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)
class Wsdl(WsdlBase):
methods = [
#
@ -63,23 +65,36 @@ class Wsdl(WsdlBase):
# описание груп (список лямбда функций)
'groups': [
lambda group: group(_("Update the system"),
normal=(),
normal=(
'cl_update_binhost_stable_opt_set',
'cl_update_binhost_recheck_set',
'cl_update_with_bdeps_opt_set',
'cl_update_binhost_choice',
'cl_update_rep_hosting_choice',
),
expert=(
'cl_update_sync_only_set',
'cl_update_other_set',
'cl_update_pretend_set',
'cl_update_sync_rep',
'cl_update_cleanpkg_set',
'cl_update_emergelist_set',
'cl_update_world',
'cl_update_egencache_force',
'cl_update_eixupdate_force',
'cl_update_wait_another_set',
'cl_update_branch',
'cl_update_autocheck_schedule_set',
'cl_templates_locate',
'cl_verbose_set', 'cl_dispatch_conf'),
next_label=_("Perform"))]},
'cl_update_other_set',
'cl_update_sync_only_set',
'cl_update_pretend_set',
'cl_update_sync_rep',
'cl_update_emergelist_set',
'cl_update_check_rep_set',
'cl_update_force_fix_set',
'cl_update_ignore_level',
'cl_update_force_level',
'cl_update_world',
'cl_update_gpg_force',
'cl_update_egencache_force',
'cl_update_eixupdate_force',
'cl_update_revdep_rebuild_set',
'cl_update_wait_another_set',
'cl_update_autocheck_schedule_set',
'cl_update_onedepth_set',
'cl_update_cleanpkg_set',
'cl_update_branch_data',
'cl_templates_locate',
'cl_verbose_set', 'cl_dispatch_conf'),
next_label=_("Run"))]},
#
# Сменить профиль
#
@ -91,7 +106,9 @@ class Wsdl(WsdlBase):
# заголовок метода
'title': __("Change the Profile"),
# иконка для графической консоли
'image': 'extension',
'image': 'calculate-update-profile,'
'notification-display-brightness-full,gtk-dialog-info,'
'help-hint',
# метод присутствует в графической консоли
'gui': True,
# консольная команда
@ -107,16 +124,15 @@ class Wsdl(WsdlBase):
'native_error': (VariableError, DataVarsError,
InstallError, UpdateError, GitError),
# значения по умолчанию для переменных этого метода
'setvars': {'cl_action!': 'update_profile'},
'setvars': {'cl_action!': 'update_profile', 'cl_update_world_default': "rebuild"},
# описание груп (список лямбда функций)
'groups': [
lambda group: group(_("Repository"),
brief=('cl_update_profile_repo_name',),
hide=('cl_update_profile_rep',
hide=('cl_update_profile_url',
'cl_update_profile_sync_set'),
normal=('cl_update_profile_rep',),
expert=('cl_update_profile_sync_set',
'cl_update_profile_branch')),
normal=('cl_update_profile_url',),
expert=('cl_update_profile_sync_set',)),
lambda group: group(_("Profile"),
normal=('cl_update_profile_system',
'cl_update_world'),
@ -131,7 +147,7 @@ class Wsdl(WsdlBase):
'cl_update_profile_linux_fullname',
'cl_update_profile_depend_data')
)],
'brief': {'next': __("Perform"),
'brief': {'next': __("Run"),
'name': __("Set the profile")}},
#
# Настроить автопроверку обновлений
@ -142,15 +158,15 @@ class Wsdl(WsdlBase):
# категория метода
'category': __('Configuration'),
# заголовок метода
'title': __("The Update Check"),
'title': __("Update Check"),
# иконка для графической консоли
'image': 'software-properties,preferences-desktop',
'image': 'calculate-setup-update,software-properties,preferences-desktop',
# метод присутствует в графической консоли
'gui': True,
# консольная команда
'command': 'cl-setup-update',
# права для запуска метода
'rights': ['update'],
'rights': ['setup_update'],
# объект содержащий модули для действия
'logic': {'Update': Update},
# описание действия

@ -3,7 +3,7 @@
# setup.py --- Setup script for calculate-update
# Copyright 2012 Calculate Ltd. http://www.calculate-linux.org
# Copyright 2012-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -18,14 +18,56 @@
# limitations under the License.
__app__ = "calculate-update"
__version__ = "3.2.2"
__version__ = "3.7.3" #TODO bump version
import os
from glob import glob
from distutils.core import setup
from calculate.install_data import install_data
from distutils.command import install_data as module_install_data
from distutils.util import change_root, convert_path
data_files = [('/usr/libexec/calculate', [('data/cl-git-wrapper', 0755)])]
class install_data(module_install_data.install_data):
def run (self):
self.mkpath(self.install_dir)
for f in self.data_files:
if isinstance(f, str):
# it's a simple file, so copy it
f = convert_path(f)
if self.warn_dir:
self.warn("setup script did not provide a directory for "
"'%s' -- installing right in '%s'" %
(f, self.install_dir))
(out, _) = self.copy_file(f, self.install_dir)
self.outfiles.append(out)
else:
# it's a tuple with path to install to and a list of files
dir = convert_path(f[0])
if not os.path.isabs(dir):
dir = os.path.join(self.install_dir, dir)
elif self.root:
dir = change_root(self.root, dir)
self.mkpath(dir)
if f[1] == []:
# If there are no files listed, the user must be
# trying to create an empty directory, so add the
# directory to the list of output files.
self.outfiles.append(dir)
else:
# Copy files, adding them to the list of output files.
for data in f[1]:
# is's a simple filename without chmod
if isinstance(data,str):
chmod = None
else:
data, chmod = data
data = convert_path(data)
(out, _) = self.copy_file(data, dir)
if chmod and os.stat(out).st_mode != chmod:
os.chmod(out,chmod)
self.outfiles.append(out)
data_files = [('/usr/libexec/calculate', [('data/cl-git-wrapper', 0o755)])]
packages = [
"calculate."+str('.'.join(root.split(os.sep)[1:]))

Loading…
Cancel
Save