'Package browser',
'description' => 'Configure package browser',
'page callback' => 'system_admin_menu_block_page',
'access arguments' => array('access apk browser'),
//system_admin_menu_block_page needs admin inc
'file' => 'system.admin.inc',
'file path' => drupal_get_path('module', 'system'),
);
$items['admin/config/apk_browser/settings'] = array(
'title' => 'Settings',
'description' => 'Configure global APK browser settings',
'page callback' => 'drupal_get_form',
'page arguments' => array('apk_browser_settings'),
'access arguments' => array('access apk browser'),
);
$items['admin/config/apk_browser/repositories'] = array(
'title' => 'Repositories',
'description' => 'Configure APK repositories',
'page callback' => 'apk_browser_repositories_list',
'access arguments' => array('access apk browser'),
);
$items['admin/config/apk_browser/repositories/add'] = array(
'title' => 'Add repository',
'description' => 'Add repositoriy',
'page callback' => 'drupal_get_form',
'page arguments' => array('apk_browser_repository_edit_form'),
'access arguments' => array('access apk browser'),
);
$items['admin/config/apk_browser/repositories/%/edit'] = array(
'title' => 'Edit repository',
'description' => 'Edit repositoriy',
'page callback' => 'drupal_get_form',
'page arguments' => array('apk_browser_repository_edit_form', 4),
'access arguments' => array('access apk browser'),
'access callback' => TRUE,
);
$items['admin/config/apk_browser/repositories/%/delete'] = array(
'title' => 'Delete repository',
'description' => 'Delete repositoriy',
'page callback' => 'drupal_get_form',
'page arguments' => array('apk_browser_repository_delete_form', 4),
'access arguments' => array('access apk browser'),
'access callback' => TRUE,
);
$items['admin/config/apk_browser/import'] = array(
'title' => 'Import packages',
'description' => 'Import APK packages',
'page callback' => 'apk_browser_import_apk',
'access arguments' => array('access apk browser'),
);
return $items;
}
function apk_browser_permission() {
return array(
'access apk browser' => array(
'title' => t('Access APK browser'),
'description' => t('Access permissions for APK browser'),
),
);
}
function apk_browser_settings($form) {
$form['settings'] = array(
'#type' => 'fieldset',
'#title' => t('APK browser settings'),
);
$form['settings']['apk_import_debug'] = array(
'#type' => 'checkbox',
'#title' => t('Import debugging'),
'#description' => t('Write import debugging to watchdog'),
'#default_value' => variable_get('apk_import_debug', '0')
);
return system_settings_form($form);
}
function apk_browser_cron() {
apk_browser_import_apk();
//cleanup old packages ones in 24h
if ((time() - variable_get('apk_browser_cleanup', '0')) > '86400') {
apk_browser_cleanup();
}
}
function apk_browser_repository_delete_form($form, &$form_state, $rid) {
$repos = variable_get('apk_repositories', array());
if (array_key_exists($rid, $repos)) {
$form['repo_id'] = array('#type' => 'hidden', '#value' => $rid);
return confirm_form($form, t('Are you sure you want to delete repository with ID @ID?', array('@ID' => $rid)), 'admin/config/apk_browser/repositories', t('This action cannot be undone.'), t('Delete repository'), t('Cancel'));
} else {
drupal_set_message(t('Please select an existing repository ID'), 'error');
drupal_goto('admin/config/apk_browser/repositories');
}
}
function apk_browser_repository_delete_form_submit($form, &$form_state) {
$repos = variable_get('apk_repositories', array());
unset($repos[$form_state['values']['repo_id']]);
variable_set('apk_repositories', array_values($repos));
drupal_set_message(t('Repository deleted'));
$form_state['redirect'] = 'admin/config/apk_browser/repositories';
}
function apk_browser_repository_edit_form($form, &$form_state, $rid = array()) {
$repos = variable_get('apk_repositories', array());
if (is_string($rid)) {
$form['repo_id'] = array('#type' => 'hidden', '#value' => $rid);
if (!array_key_exists($rid, $repos)) {
drupal_set_message(t('Please select an existing repository ID'), 'error');
drupal_goto('admin/config/apk_browser/repositories');
}
}
$repo = taxonomy_vocabulary_machine_name_load('apk_repo');
$repo_tree = taxonomy_get_tree($repo->vid);
$arch = taxonomy_vocabulary_machine_name_load('apk_arch');
$arch_tree = taxonomy_get_tree($arch->vid);
if (empty($arch_tree) || empty($repo_tree)) {
$form['warning'] = array(
'#type' => 'item',
'#markup' => '
Please add terms to you your repositories and architectures
'
. l('Manage Taxonomy', 'admin/structure/taxonomy')
);
} else {
$form['settings'] = array(
'#type' => 'fieldset',
'#title' => t('Repository settings'),
);
$form['settings']['apk_repo_url'] = array(
'#type' => 'textfield',
'#title' => t('Repository URL'),
'#size' => '100',
'#default_value' => is_string($rid) ? $repos[$rid]['url'] : '',
'#description' => t('Link to the APKINDEX.tar.gz file inside the repository'),
'#required' => TRUE,
);
foreach ($repo_tree as $key => $term) {
$repo_options[$term->tid] = $term->name;
}
//generate a form item to select terms
$form['settings']['repo'] = array(
'#type' => 'select',
'#options' => $repo_options,
'#title' => 'Select repository',
'#default_value' => is_string($rid) ? $repos[$rid]['repo'] : '',
'#description' => t('Please make sure you select the correct repository.
Preventing to do so will break package listing')
);
foreach ($arch_tree as $key => $term) {
$arch_options[$term->tid] = $term->name;
}
$form['settings']['arch'] = array(
'#type' => 'select',
'#options' => $arch_options,
'#default_value' => is_string($rid) ? $repos[$rid]['arch'] : '',
'#title' => 'Select architecture',
'#description' => t('Please make sure you select the correct architecture.
Preventing to dox so will break package listing')
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save repository')
);
}
return $form;
}
function apk_browser_repository_edit_form_validate($form, &$form_state) {
if (!valid_url($form_state['values']['apk_repo_url'], TRUE)) {
form_set_error('apk_repo_url', t('Please specify a valid URL!'));
}
//foreach (variable_get('apk_repositories', array()) as $repo) {
// if ($form_state['values']['apk_repo_url'] == $repo['url']) {
// form_set_error('apk_repo_url', t('You are trying to add a double url'));
// }
// }
}
function apk_browser_repository_edit_form_submit($form, &$form_state) {
$repos = array(
'url' => $form_state['values']['apk_repo_url'],
'arch' => $form_state['values']['arch'],
'repo' => $form_state['values']['repo']
);
$repo_db = variable_get('apk_repositories', array());
isset($form_state['values']['repo_id']) ? ( $repo_db[$form_state['values']['repo_id']] = $repos) : ($repo_db[] = $repos);
variable_set('apk_repositories', $repo_db);
drupal_set_message(t('Repositories are saved'));
$form_state['redirect'] = 'admin/config/apk_browser/repositories';
//drupal_set_message(t('Submitting values: @values', array('@values' => var_export($form_state, TRUE))));
}
function apk_browser_repositories_list() {
$terms = '';
$header = array('ID', 'URL', 'Repo', 'Arch', array('data' => 'Manage', 'colspan' => '2'));
$add = l('', 'admin/config/apk_browser/repositories/add', array('html' => TRUE));
foreach (variable_get('apk_repositories', array()) as $key => $repo) {
$edit = l('', 'admin/config/apk_browser/repositories/' . $key . '/edit', array('html' => TRUE));
$delete = l('', 'admin/config/apk_browser/repositories/' . $key . '/delete', array('html' => TRUE));
$terms = '';
$tarch = taxonomy_term_load($repo['arch']);
$trepo = taxonomy_term_load($repo['repo']);
$rows[] = array($key, $repo['url'], $trepo->name, $tarch->name, $edit, $delete);
}
if (!empty($rows)) {
$output = theme('table', array('header' => $header, 'rows' => $rows));
$output .= $add;
return $output;
} else {
return t('No repositories added. Please add one.') . ' ' . $add;
}
}
function apk_browser_import_apk() {
$repos = variable_get('apk_repositories', NULL);
$debug = variable_get('apk_import_debug', '0');
$updated = array();
$added = array();
$skipped = '0';
foreach ($repos as $id => $repo) {
$apk_string = file_get_contents($repo['url']);
//no need to update if apkindex is not changed
$checksum = md5($apk_string);
$checksum_db = variable_get('apk_import_checksum_' . $id, '');
if (($debug == '1') || ($checksum != $checksum_db)) {
if ($debug == '1') {
watchdog('apk', 'APK importer running in debug mode!', array(), WATCHDOG_WARNING, NULL);
}
$packages = apk_browser_apkindex_reader($apk_string);
foreach ($packages as $package) {
$exist = apk_browser_apk_check($package['P'], $repo['arch'], $repo['repo']);
if ($exist) {
//check if both fields are set
if (count($exist) == '2') {
//if checksum has changed we set nid and update node
if ($exist['csum'] != $package['C']) {
$package['update'] = $exist['nid'];
$updated[] = apk_browser_add_apk($package, $repo['arch'], $repo['repo']);
//log to db if we are debugging
if ($debug == '1') {
watchdog('apk', 'Package @apk updated', array('@apk' => $package['P']), WATCHDOG_INFO, NULL);
}
} else {
if ($debug == '1') {
watchdog('apk', 'Package @apk already in database', array('@apk' => $package['P']), WATCHDOG_INFO, NULL);
}
$skipped++;
}
} else {
watchdog('apk', 'Package @apk has issues', array('@apk' => $package['P']), WATCHDOG_ERROR, NULL);
}
// this is a new package
} else {
$added[] = apk_browser_add_apk($package, $repo['arch'], $repo['repo']);
if ($debug == '1') {
watchdog('apk', 'Package @apk added', array('@apk' => $package['P']), WATCHDOG_INFO, NULL);
}
}
}
watchdog('apk', 'Added @added and updated @updated and skipped @skipped packages', array('@added' => count($added), '@updated' => count($updated), '@skipped' => $skipped), WATCHDOG_INFO, NULL);
variable_set('apk_import_checksum_' . $id, $checksum);
} else {
watchdog('apk', 'APK index is up-to-date', array(), WATCHDOG_INFO, NULL);
}
}
}
function apk_browser_add_apk($package, $arch_tid, $repo_tid) {
/*
* 1st array key is language, currently set to und
* second key is for multi value fields like depend
* php timeout set to 10min for initial repo import
*/
$repo = taxonomy_term_load($repo_tid);
$arch = taxonomy_term_load($arch_tid);
ini_set('max_execution_time', 600);
$node = (isset($package['update'])) ? node_load($package['update']) : new stdClass();
$node->type = 'apk';
foreach ($package as $field => $value) {
switch ($field) {
case 'P':
$node->title = $value;
break;
case 'C':
$node->apk_checksum['und'][0]['value'] = $value;
break;
case 'V':
$node->apk_version['und'][0]['value'] = $value;
break;
case 'S':
$node->apk_size['und'][0]['value'] = $value;
break;
case 'I':
$node->apk_isize['und'][0]['value'] = $value;
break;
case 'T':
$node->apk_description['und'][0]['value'] = $value;
break;
case 'U':
$node->apk_url['und'][0]['value'] = $value;
break;
case 'L':
$node->apk_license['und'][0]['value'] = $value;
break;
//setting this will update package with this nid number
case 'update':
$node->revision = '1';
$node->nid = $value;
break;
case 'D':
//do not keep old depends.
unset($node->apk_depends['und']);
foreach ($value as $depend) {
$node->apk_depends['und'][]['value'] = $depend;
}
break;
}
}
$node->taxonomy_apk_repo['und'][0]['tid'] = $repo_tid;
$node->taxonomy_apk_arch['und'][0]['tid'] = $arch_tid;
$node->language = 'und';
$node = node_submit($node);
node_save($node);
//create a human path based on repo and arch
$alias = array(
'source' => 'node' . '/' . $node->nid,
'alias' => 'apk/' . $repo->name . '/' . $arch->name . '/' . $node->title
);
path_save($alias);
return $node->nid;
}
function apk_browser_apkindex_reader($apk_string) {
//file_get_contents outputs string and php tar needs file
$temp = tempnam("/tmp", "apk_");
$fp = fopen($temp, 'w');
fwrite($fp, $apk_string);
fclose($fp);
$tar_object = new Archive_Tar($temp);
$apkindex = $tar_object->extractInString("APKINDEX");
//convert packages data into array
$packages = preg_split("`\n\W+`", $apkindex);
//remove last empty line
array_pop($packages);
foreach ($packages as $key => $package) {
//convert packages lines into array
$apackage = preg_split("`\n`", $package);
foreach ($apackage as $apk_line) {
switch ($apk_line[0]) {
//depend and install_if lines
case "D":
case "i":
$result[$key][$apk_line[0]] = explode(" ", substr($apk_line, 2));
break;
//other apk variables
default:
$result[$key][$apk_line[0]] = substr($apk_line, 2);
}
}
}
/*
* this should take care of having multiple packages with different
* version in repositories
*/
foreach ($result as $pkg) {
//check if package name already exit in result array
if (isset($output[$pkg['P']])) {
//check if version number is higher then previous
if ($output[$pkg['P']]['V'] < $pkg['V']) {
//if version is higher we overwrite package
$output[$pkg['P']] = $pkg;
}
//all not matching packages we just write them
} else {
$output[$pkg['P']] = $pkg;
}
}
unlink($temp);
return $output;
}
function apk_browser_apk_check($title, $arch, $repo) {
/* query using 4 tables to fetch nid and checksum
* used to check if apk exist and return checksum
* to check if its updated
*/
return db_query("
SELECT node.nid,
csum.apk_checksum_value as csum
FROM {node},
{field_data_taxonomy_apk_arch} AS arch,
{field_data_taxonomy_apk_repo} AS repo,
{field_data_apk_checksum} AS csum
WHERE node.nid = arch.entity_id
AND node.nid = repo.entity_id
AND node.nid = csum.entity_id
AND node.title = :title
AND arch.taxonomy_apk_arch_tid = :arch
AND repo.taxonomy_apk_repo_tid = :repo", array(
':title' => $title,
':arch' => $arch,
':repo' => $repo
)
)->fetchAssoc();
}
//this is needed for auto views integration
function apk_browser_views_api() {
return array('api' => 3.0);
}
//disable packages which seem to have gone
function apk_browser_cleanup() {
//load repo's and clean them
$repos = variable_get('apk_repositories', NULL);
watchdog('apk', 'Starting apk cleanup', array(), WATCHDOG_INFO, NULL);
foreach ($repos as $id => $repo) {
//create array from apkindex and filter out name
$apk_string = file_get_contents($repo['url']);
$packages = apk_browser_apkindex_reader($apk_string);
foreach ($packages as $package) {
$apk_names[] = $package['P'];
}
//fetch all packages from db matching arch repo
$db_apk = db_query("
SELECT node.nid, node.title
FROM {node},
{field_data_taxonomy_apk_arch} AS arch,
{field_data_taxonomy_apk_repo} AS repo
WHERE node.nid = arch.entity_id
AND node.nid = repo.entity_id
AND node.status = '1'
AND arch.taxonomy_apk_arch_tid = :arch
AND repo.taxonomy_apk_repo_tid = :repo", array(
':arch' => $repo['arch'],
':repo' => $repo['repo']
)
)->fetchAllKeyed(0, 1);
//check which packages are in db but not in repo
$diff = array_diff($db_apk, $apk_names);
foreach ($diff as $nid => $disable) {
//load the node and unpublish it
$node = node_load($nid);
$node->status = '0';
$node = node_submit($node);
node_save($node);
watchdog('apk', 'Package @apk has been disabled', array('@apk' => $disable), WATCHDOG_INFO, NULL);
}
}
//record timestamp so we can run this function based on age
variable_set('apk_browser_cleanup', time());
}