'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_importer', '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_delete_all_apk_limit'] = array( '#type' => 'textfield', '#size' => '4', '#title' => t('Node delete batch limit'), '#description' => t('While deleting all packages, we need to limit the batch process. If it times out please lower this value.'), '#default_value' => variable_get('apk_import_delete_all_apk_limit', '20') ); $form['settings']['apk_import_commit_url'] = array( '#type' => 'textfield', '#title' => t('Commit URL'), '#description' => t('URL to construct a link to repository browser. Use %commit% to be replaced with the actual commit'), '#default_value' => variable_get('apk_import_commit_url', '') ); $form['settings']['apk_import_status'] = array( '#type' => 'checkbox', '#title' => t('Enable apk import'), '#description' => t('Enable or disable package importing and cleanup'), '#default_value' => variable_get('apk_import_status', '0') ); $feeds = array('0' => t('Disabled')); if (module_exists('aggregator')) { $feeds += db_query("SELECT fid,title FROM {aggregator_feed}")->fetchAllKeyed(); $disabled = FALSE; $description = t('When enabled, commit title will be searched in this aggregator feed.'); } else { $disabled = TRUE; $description = t('You need to have aggregator enabled and configured to enable this.'); } $form['settings']['apk_import_aggregator'] = array( '#type' => 'select', '#title' => t('RSS commit lookup'), '#description' => $description, '#default_value' => variable_get('apk_import_aggregator', '0'), '#options' => $feeds, '#disabled' => $disabled, ); return system_settings_form($form); } function apk_browser_cron() { if (variable_get('apk_import_status', '0')) { apk_browser_importer(); //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 = ''; $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_importer() { $repos = variable_get('apk_repositories', NULL); if ($repos) { foreach ($repos as $id => $repo) { $apk_string = file_get_contents($repo['url']); $checksum = md5($apk_string); $checksum_db = variable_get('apk_import_checksum_' . $id, ''); if ($checksum != $checksum_db) { $repo_apks = apk_browser_apkindex_reader($apk_string); $skipped = '0'; $updated = array(); $added = array(); $db_apks = db_query(" SELECT node.nid, node.title, 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.status = '1' AND node.nid = csum.entity_id AND arch.taxonomy_apk_arch_tid = :arch AND repo.taxonomy_apk_repo_tid = :repo", array( ':arch' => $repo['arch'], ':repo' => $repo['repo'] ) )->fetchAllAssoc('title'); foreach ($repo_apks as $title => $repo_apk) { if (isset($db_apks[$title])) { if ($repo_apk['C'] != $db_apks[$title]->csum) { $repo_apk['update'] = $db_apks[$title]->nid; $updated[] = apk_browser_add_apk($repo_apk, $repo['arch'], $repo['repo']); } else { $skipped++; } } else { $added[] = apk_browser_add_apk($repo_apk, $repo['arch'], $repo['repo']); } } watchdog('apk', 'Added @added and updated @updated and skipped @skipped packages for repo @repo', array('@added' => count($added), '@updated' => count($updated), '@skipped' => $skipped, '@repo' => $id), WATCHDOG_INFO, NULL); variable_set('apk_import_checksum_' . $id, $checksum); } } } else { watchdog('apk', 'No repositories found. Please add them first', array(), WATCHDOG_WARNING, 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'; //set default value for build time. some pkg do not have ts and will not sort right. $node->apk_build_time['und'][0]['value'] = '0'; 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_installed_size['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; case 'o': $node->apk_origin['und'][0]['value'] = $value; break; case 'm': $node->apk_maintainer['und'][0]['value'] = $value; break; case 't': $node->apk_build_time['und'][0]['value'] = $value; break; case 'c': $node->apk_commit['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; case 'i': //do not keep old install if. unset($node->apk_install_if['und']); foreach ($value as $installif) { $node->apk_install_if['und'][]['value'] = $installif; } 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; } //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); if ($repos) { 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()); } else { watchdog('apk', 'No repositories found. Please add them first', array(), WATCHDOG_WARNING, NULL); } } //create an url from the commit, and if we find it in aggregator we add a title function apk_browser_commit_url($commit) { $fid = variable_get('apk_import_aggregator', '0'); $attributes = array(); $href = str_replace('%commit%', $commit, variable_get('apk_import_commit_url', '')); if (module_exists('aggregator') && $fid) { if (!empty($commit)) { $search = '%' . $commit . '%'; } $title = db_query("SELECT title FROM {aggregator_item} WHERE link LIKE :commit AND fid = :fid", array(':commit' => $search, ':fid' => $fid))->fetchField(); if ($title) { $attributes = array('title' => check_plain($title), 'class' => 'with-tooltip'); $path = drupal_get_path('module', 'apk_browser'); drupal_add_js($path . '/tooltip/tooltip.js'); drupal_add_js('jQuery().ready(function(){jQuery(".with-tooltip").simpletooltip();});', 'inline'); } } return l($commit, $href, array('attributes' => $attributes)); } //format the fields into a table function apk_browser_package_table($vars) { $empty = '

' . t('None') . '

'; //prepare depends fields $depends = ''; if (!empty($vars['apk_depends'])) { foreach ($vars['apk_depends'] as $dep) { $value = trim($dep['value'], '!'); $value = preg_split("/[<>=]/", $value); $depends .= '

' . l($dep['value'], 'apk/' . $vars['taxonomy_apk_repo']['0']['taxonomy_term']->name . '/' . $vars['taxonomy_apk_arch']['0']['taxonomy_term']->name . '/' . $value[0], array()) . '

'; } } else { $depends = $empty; } //prepare install if fields $installif = ''; if (!empty($vars['apk_install_if'])) { foreach ($vars['apk_install_if'] as $iif) { $value = trim($iif['value'], '!'); $value = preg_split("/[<>=]/", $value); $installif .= '

' . l($iif['value'], 'apk/' . $vars['taxonomy_apk_repo']['0']['taxonomy_term']->name . '/' . $vars['taxonomy_apk_arch']['0']['taxonomy_term']->name . '/' . $value[0], array()) . '

'; } } //prepare origin link if (!empty($vars['apk_origin'])) { $value = trim($vars['apk_origin'][0]['value'], '!'); $value = preg_split("/[<>=]/", $value); $origin = l($vars['apk_origin'][0]['safe_value'], 'apk/' . $vars['taxonomy_apk_repo']['0']['taxonomy_term']->name . '/' . $vars['taxonomy_apk_arch']['0']['taxonomy_term']->name . '/' . $vars['apk_origin'][0]['value'] ); } else { $origin = $empty; } //prepare all fields $fields[t('Package')] = $vars['title']; $fields[t('Version')] = $vars['apk_version'][0]['safe_value']; $fields[t('Architecture')] = $vars['taxonomy_apk_arch'][0]['taxonomy_term']->name; $fields[t('Repository')] = $vars['taxonomy_apk_repo'][0]['taxonomy_term']->name; $fields[t('Project')] = l('URL', $vars['apk_url'][0]['safe_value']); $fields[t('Description')] = $vars['apk_description'][0]['safe_value']; $fields[t('License')] = $vars['apk_license'][0]['safe_value']; $fields[t('Maintainer')] = empty($vars['apk_maintainer']) ? $empty : $vars['apk_maintainer'][0]['safe_value']; $fields[t('Build date')] = empty($vars['apk_build_time']) ? $empty : date('r', $vars['apk_build_time'][0]['value']); $fields[t('Commit')] = empty($vars['apk_commit']) ? $empty : apk_browser_commit_url($vars['apk_commit']['0']['value']); //only show origin if its different then itself if (!empty($vars['apk_origin']) && ($vars['apk_origin'][0]['value'] != $vars['title'])) { $fields[t('Origin')] = $origin; } //to prevent misunderstanding only display when needed. $fields[t('Depends')] = $depends; if (!empty($installif)) { $fields[t('Install if')] = $installif; } //setup array for theme function foreach ($fields as $header => $cell) { $table['rows'][]['data'] = array(array('data' => $header, 'header' => 'header'), $cell); } //render the table return theme('table', $table); } //create template variable with package table function apk_browser_preprocess_node(&$vars) { if ($vars['node']->type == 'apk') { $vars['apk_table'] = apk_browser_package_table($vars); } } function apk_browser_origin_list() { //Get a loaded object from a router item. $node = menu_get_object(); //Some older packages are missing origin field. if (isset($node->apk_origin)) { $rows = db_query(" SELECT node.title FROM {node} LEFT JOIN {field_data_apk_origin} as origin ON (node.nid = origin.entity_id) LEFT JOIN {field_data_taxonomy_apk_arch} as arch ON (node.nid = arch.entity_id) LEFT JOIN {field_data_taxonomy_apk_repo} as repo ON (node.nid = repo.entity_id) WHERE origin.apk_origin_value = :origin AND arch.taxonomy_apk_arch_tid = :arch AND repo.taxonomy_apk_repo_tid = :repo AND node.status = '1'", array( ':origin' => $node->apk_origin['und']['0']['value'], ':arch' => $node->taxonomy_apk_arch['und']['0']['tid'], ':repo' => $node->taxonomy_apk_repo['und']['0']['tid'] ) )->fetchCol(); if ($rows) { foreach ($rows as $key => $row) { $link = l($row, 'apk/' . $node->taxonomy_apk_repo['und']['0']['taxonomy_term']->name . '/' . $node->taxonomy_apk_arch['und']['0']['taxonomy_term']->name . '/' . $row ); $list['items'][]['data'] = $link; } return theme('item_list', $list); } } return FALSE; } function apk_browser_reverse_depends() { $node = menu_get_object(); $rows = db_query(" SELECT node.title FROM {node} LEFT JOIN {field_data_apk_depends} as deps ON (node.nid = deps.entity_id) LEFT JOIN {field_data_taxonomy_apk_arch} as arch ON (node.nid = arch.entity_id) LEFT JOIN {field_data_taxonomy_apk_repo} as repo ON (node.nid = repo.entity_id) WHERE deps.apk_depends_value = :deps AND arch.taxonomy_apk_arch_tid = :arch AND repo.taxonomy_apk_repo_tid = :repo AND node.status = '1' ", array( ':deps' => $node->title, ':arch' => $node->taxonomy_apk_arch['und']['0']['tid'], ':repo' => $node->taxonomy_apk_repo['und']['0']['tid'] ) )->fetchCol(); if ($rows) { foreach ($rows as $key => $row) { $link = l($row, 'apk/' . $node->taxonomy_apk_repo['und']['0']['taxonomy_term']->name . '/' . $node->taxonomy_apk_arch['und']['0']['taxonomy_term']->name . '/' . $row ); $list['items'][]['data'] = $link; } return theme('item_list', $list); } else { return FALSE; } } function apk_browser_block_info() { $blocks['apk_browser_origin'] = array( 'info' => t('Packages with same origin'), 'visibility' => BLOCK_VISIBILITY_LISTED, // This block should only display on pages starting with apk. 'pages' => 'apk/*' ); $blocks['apk_browser_reverse_depends'] = array( 'info' => t('Package reverse dependicies'), 'visibility' => BLOCK_VISIBILITY_LISTED, 'pages' => 'apk/*' ); return $blocks; } function apk_browser_block_view($delta = '') { $js = 'jQuery(document).ready(function() { jQuery(".block-apk-browser .block-content").hide(); jQuery(".block-apk-browser .block-title").click(function() { jQuery(this).next(".block-apk-browser .block-content").slideToggle(500); return FALSE; }); });'; //The $delta parameter tells us which block is being requested. switch ($delta) { case 'apk_browser_origin': $origin = apk_browser_origin_list(); if ($origin) { $block['subject'] = t('Same origin'); $block['content'] = $origin; } break; case 'apk_browser_reverse_depends': $revdep = apk_browser_reverse_depends(); if ($revdep) { $block['subject'] = t('Reverse dependecies'); $block['content'] = $revdep; } } // If one of the functions above returns true, we add collapse script if (($origin) || ($revdep)) { drupal_add_js($js, 'inline'); } return ($block['content']) ? $block : FALSE; }