'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 = ''; $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()); }