Browse code

Make the module more flexible, add a "rsync" feature.

Xavier G authored on 03/05/2012 17:15:00
Showing 2 changed files

... ...
@@ -4,5 +4,5 @@ description = "Use your TuxFamily repository to store uploaded files."
4 4
 core = 7.x
5 5
 package = Performance and scalability
6 6
 configure = admin/config/media/file-system
7
-version = 7.x-1.0beta
7
+version = 7.x-2.0beta
8 8
 
... ...
@@ -1,21 +1,21 @@
1 1
 <?php
2 2
 /*
3
-  Copyright 2011 Xavier Guerrin and the TuxFamily team
3
+  Copyright 2011-2012 Xavier Guerrin and the TuxFamily team
4 4
   This file is part of the tuxfamily_repository module.
5
-  
5
+
6 6
   This module is free software: you can redistribute it and/or modify
7 7
   it under the terms of the GNU General Public License as published by
8 8
   the Free Software Foundation, either version 2 of the License, or
9 9
   (at your option) any later version.
10
-  
10
+
11 11
   This module is distributed in the hope that it will be useful,
12 12
   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 14
   GNU General Public License for more details.
15
-  
15
+
16 16
   You should have received a copy of the GNU General Public License
17 17
   along with this module.  If not, see <http://www.gnu.org/licenses/>.
18
-*/
18
+ */
19 19
 /**
20 20
  * @file
21 21
  * Implements the core hooks, defines, public and private functions.
... ...
@@ -27,30 +27,68 @@ define('TUXFAMILY_REPOSITORY_DEFAULT_ENABLED',              FALSE);
27 27
 define('TUXFAMILY_REPOSITORY_PROJECT_VARIABLE',             'tuxfamily_project');
28 28
 define('TUXFAMILY_REPOSITORY_DEFAULT_PROJECT',              'project');
29 29
 
30
-define('TUXFAMILY_REPOSITORY_DRUPAL_PATH_VARIABLE',         'tuxfamily_repository_drupal_path');
31
-define('TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH',          'drupal_files');
30
+define('TUXFAMILY_REPOSITORY_DRUPAL_STATIC_PATH_VARIABLE',  'tuxfamily_repository_drupal_static_path');
31
+define('TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_STATIC_PATH',   'drupal_files/static');
32
+
33
+define('TUXFAMILY_REPOSITORY_DRUPAL_FILES_PATH_VARIABLE',   'tuxfamily_repository_drupal_files_path');
34
+define('TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_FILES_PATH',    'drupal_files/sites/@sitename/files');
32 35
 
33 36
 define('TUXFAMILY_REPOSITORY_BASE_URL_PATTERN_VARIABLE',    'tuxfamily_repository_base_url_pattern');
34
-define('TUXFAMILY_REPOSITORY_DEFAULT_BASE_URL_PATTERN',     'http://download.tuxfamily.org/%1$s/%2$s');
37
+define('TUXFAMILY_REPOSITORY_DEFAULT_BASE_URL_PATTERN',     'http://download.tuxfamily.org/@project/@path');
35 38
 
36 39
 define('TUXFAMILY_REPOSITORY_DRUPAL_PATH_PATTERN_VARIABLE', 'tuxfamily_repository_drupal_path_pattern');
37
-define('TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH_PATTERN',  '/data/repository/%1$s/%2$s');
40
+define('TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH_PATTERN',  '/data/repository/@project/@path');
41
+
42
+define('TUXFAMILY_REPOSITORY_SSH_PATH_PATTERN_VARIABLE',    'tuxfamily_repository_ssh_path_pattern');
43
+define('TUXFAMILY_REPOSITORY_DEFAULT_SSH_PATH_PATTERN',     '/home/@project/@project-repository/@path');
44
+
45
+define('TUXFAMILY_REPOSITORY_RSYNC_PATH_VARIABLE',          'tuxfamily_repository_rsync_path');
46
+define('TUXFAMILY_REPOSITORY_DEFAULT_RSYNC_PATH',           '/usr/bin/rsync');
38 47
 
39 48
 /**
40
- * Implements hook_alter().
41
- * Used here to modify generated file urls.
49
+ * Implements hook_boot().
50
+ * Used to adjust the public_file_path variable in case Drupal is run from
51
+ * TuxFamily's SSH access.
42 52
  */
43
-function tuxfamily_repository_file_url_alter(&$path) {
44
-  static $tf_repos_enabled = -1;
45
-  if ($tf_repos_enabled === -1) {
46
-    $tf_repos_enabled = variable_get(TUXFAMILY_REPOSITORY_ENABLED_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_ENABLED);
53
+function tuxfamily_repository_boot() {
54
+  // Not configurable yet, this arbitrary condition checks whether we are run through SSH / Cron
55
+  if (isset($_SERVER['HOME']) && $_SERVER['HOME'] == '/home') {
56
+    $GLOBALS['conf']['tuxfamily_ssh'] = TRUE;
57
+    $GLOBALS['conf']['file_public_path'] = _tuxfamily_repository_get_files_base_path();
47 58
   }
48
-  if (!$tf_repos_enabled) {
59
+}
60
+
61
+/**
62
+ * Implements hook_modules_installed().
63
+ * Used here to sync static files to the TuxFamily repository.
64
+ */
65
+function tuxfamily_repository_modules_installed($modules_installed) {
66
+  // do nothing when the module itself is enabled/installed (i.e. probably not configured yet)
67
+  if (in_array('tuxfamily_repository', $modules_installed)) {
68
+    return;
69
+  }
70
+
71
+  _tuxfamily_repository_rsync_static_files();
72
+}
73
+
74
+/**
75
+ * Implements hook_file_url_alter().
76
+ * Used here to modify generated file URLs.
77
+ */
78
+function tuxfamily_repository_file_url_alter(&$path) {
79
+  $enabled = variable_get(TUXFAMILY_REPOSITORY_ENABLED_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_ENABLED);
80
+  if (!$enabled) {
49 81
     return;
50 82
   }
83
+  $data = drupal_static(__FUNCTION__);
84
+  if (!$data) {
85
+    $data['static_url'] = _tuxfamily_repository_get_static_base_url();
86
+    $data['files_url']  = _tuxfamily_repository_get_files_base_url();
87
+    $data['files_path'] = _tuxfamily_repository_get_files_base_path();
88
+  }
51 89
 
52
-  // We are only interested into public:// URIs
53
-  if (!preg_match('/^public:\/\//', $path)) {
90
+  // We do not alter private files
91
+  if (preg_match('/^(private|http):\/\//', $path)) {
54 92
     return;
55 93
   }
56 94
 
... ...
@@ -67,7 +105,26 @@ function tuxfamily_repository_file_url_alter(&$path) {
67 105
   }
68 106
 
69 107
   // Eventually, we replace public:// (9 characters) with our base url.
70
-  $path = _tuxfamily_repository_get_base_url() . '/' . substr($path, 9);
108
+  if (preg_match('/^public:.*$/', $path)) {
109
+    $path = $data['files_url'] . '/' . substr($path, 9);
110
+  }
111
+  else {
112
+    $path = $data['static_url'] . '/' . ltrim($path, '/');
113
+  }
114
+}
115
+
116
+/**
117
+ * Implements hook_menu().
118
+ */
119
+function tuxfamily_repository_menu() {
120
+  $items['admin/config/tuxfamily'] = array(
121
+    'title' => 'TuxFamily repository',
122
+    'page callback' => 'drupal_get_form',
123
+    'page arguments' => array('_tuxfamily_repository_config'),
124
+    'access arguments' => array('administer site configuration'),
125
+    'type' => MENU_CALLBACK,
126
+  );
127
+  return $items;
71 128
 }
72 129
 
73 130
 /**
... ...
@@ -79,26 +136,84 @@ function tuxfamily_repository_form_alter(&$form, &$form_state, $form_id) {
79 136
   }
80 137
 }
81 138
 
82
-function _tuxfamily_repository_get_base_url() {
83
-  $repository_base_url = sprintf(
84
-    variable_get(TUXFAMILY_REPOSITORY_BASE_URL_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_BASE_URL_PATTERN),
85
-    variable_get(TUXFAMILY_REPOSITORY_PROJECT_VARIABLE,          TUXFAMILY_REPOSITORY_DEFAULT_PROJECT),
86
-    variable_get(TUXFAMILY_REPOSITORY_DRUPAL_PATH_VARIABLE,      TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH)
87
-  );
88
-  return rtrim($repository_base_url, '/');
139
+/**
140
+ * @return the pattern to be used in order to determine the absolute path to
141
+ * the TuxFamily repository. Note the result depends on the environment: web
142
+ * server or SSH server.
143
+ */
144
+function _tuxfamily_repository_get_repository_path_pattern() {
145
+  if ($GLOBALS['conf']['tuxfamily_ssh']) {
146
+    $path = variable_get(TUXFAMILY_REPOSITORY_SSH_PATH_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_SSH_PATH_PATTERN);
147
+  }
148
+  else {
149
+    $path = variable_get(TUXFAMILY_REPOSITORY_DRUPAL_PATH_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH_PATTERN);
150
+  }
151
+  return $path;
89 152
 }
90 153
 
91
-function _tuxfamily_repository_get_file_public_path() {
92
-  $file_public_path = sprintf(
93
-    variable_get(TUXFAMILY_REPOSITORY_DRUPAL_PATH_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH_PATTERN),
94
-    variable_get(TUXFAMILY_REPOSITORY_PROJECT_VARIABLE,             TUXFAMILY_REPOSITORY_DEFAULT_PROJECT),
95
-    variable_get(TUXFAMILY_REPOSITORY_DRUPAL_PATH_VARIABLE,         TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH)
96
-  );
97
-  return $file_public_path;
154
+/**
155
+ * @return the path, relative to the TuxFamily repository, to the directory
156
+ * that will host static content, i.e. content provided by Drupal modules.
157
+ */
158
+function _tuxfamily_repository_get_static_path() {
159
+  $static_path = variable_get(TUXFAMILY_REPOSITORY_DRUPAL_STATIC_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_STATIC_PATH);
160
+  $static_path = _tuxfamily_repository_apply_tokens($static_path);
161
+  return $static_path;
162
+}
163
+
164
+/**
165
+ * @return the path, relative to the TuxFamily repository, to the directory
166
+ * that will host the Drupal files, i.e. uploaded and dynamically generated files.
167
+ */
168
+function _tuxfamily_repository_get_files_path() {
169
+  $files_path = variable_get(TUXFAMILY_REPOSITORY_DRUPAL_FILES_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_FILES_PATH);
170
+  $files_path = _tuxfamily_repository_apply_tokens($files_path);
171
+  return $files_path;
172
+}
173
+
174
+/**
175
+ * @return the base URL to the directory that will host the Drupal files, i.e.
176
+ * uploaded and dynamically generated files.
177
+ */
178
+function _tuxfamily_repository_get_files_base_url() {
179
+  $url = variable_get(TUXFAMILY_REPOSITORY_BASE_URL_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_BASE_URL_PATTERN);
180
+  $url = _tuxfamily_repository_apply_tokens($url, array('@path' => _tuxfamily_repository_get_files_path()));
181
+  return $url;
182
+}
183
+
184
+/**
185
+ * @return the base URL to the directory that will host static content, i.e.
186
+ * content provided by Drupal modules.
187
+ */
188
+function _tuxfamily_repository_get_static_base_url() {
189
+  $url = variable_get(TUXFAMILY_REPOSITORY_BASE_URL_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_BASE_URL_PATTERN);
190
+  $url = _tuxfamily_repository_apply_tokens($url, array('@path' => _tuxfamily_repository_get_static_path()));
191
+  return $url;
192
+}
193
+
194
+/**
195
+ * @return the absolute path to the directory that will host the Drupal files,
196
+ * i.e. uploaded and dynamically generated files.
197
+ */
198
+function _tuxfamily_repository_get_files_base_path() {
199
+  $path = _tuxfamily_repository_get_repository_path_pattern();
200
+  $path = _tuxfamily_repository_apply_tokens($path, array('@path' => _tuxfamily_repository_get_files_path()));
201
+  return $path;
202
+}
203
+
204
+/**
205
+ * @return the absolute path to the directory that will host static content, i.e.
206
+ * content provided by Drupal modules.
207
+ */
208
+function _tuxfamily_repository_get_static_base_path() {
209
+  $path = _tuxfamily_repository_get_repository_path_pattern();
210
+  $path = _tuxfamily_repository_apply_tokens($path, array('@path' => _tuxfamily_repository_get_static_path()));
211
+  return $path;
98 212
 }
99 213
 
100 214
 /**
101
- * Modifies the 'file_system_settings' form to let the user enable and configure its TuxFamily repository.
215
+ * Modify the 'file_system_settings' form to let the user enable and configure
216
+ * its TuxFamily repository.
102 217
  */
103 218
 function _tuxfamily_repository_alter_file_system_settings(&$form, &$form_state, $form_id) {
104 219
   // we add our own submit function
... ...
@@ -108,7 +223,7 @@ function _tuxfamily_repository_alter_file_system_settings(&$form, &$form_state,
108 223
   $tf_repos_enabled = variable_get(TUXFAMILY_REPOSITORY_ENABLED_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_ENABLED);
109 224
 
110 225
   if ($tf_repos_enabled) {
111
-    $form['file_public_path']['#default_value'] = _tuxfamily_repository_get_file_public_path();
226
+    $form['file_public_path']['#default_value'] = _tuxfamily_repository_get_files_base_path();
112 227
     $form['file_public_path']['#description'] .= ' ' . t('Note its value is computed by the TuxFamily repository module');
113 228
   }
114 229
 
... ...
@@ -127,6 +242,9 @@ function _tuxfamily_repository_alter_file_system_settings(&$form, &$form_state,
127 242
     '#collapsed' => !$tf_repos_enabled,
128 243
     '#weight' => -999,
129 244
   );
245
+  $form['tuxfamily_repository_options']['tokens'] = array(
246
+    '#markup' => 'The following tokens are available for paths: @sitename (e.g. default if you are not using a multisite setup), @drupalroot (path to the Drupal directory) and @project (value filled in the first field).',
247
+  );
130 248
   $form['tuxfamily_repository_options'][TUXFAMILY_REPOSITORY_PROJECT_VARIABLE] = array(
131 249
     '#type' => 'textfield',
132 250
     '#title' => t('Name of your TuxFamily project'),
... ...
@@ -134,23 +252,36 @@ function _tuxfamily_repository_alter_file_system_settings(&$form, &$form_state,
134 252
     '#required' => TRUE,
135 253
     '#default_value' => variable_get(TUXFAMILY_REPOSITORY_PROJECT_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_PROJECT),
136 254
   );
137
-  $form['tuxfamily_repository_options'][TUXFAMILY_REPOSITORY_DRUPAL_PATH_VARIABLE] = array(
255
+  $form['tuxfamily_repository_options'][TUXFAMILY_REPOSITORY_DRUPAL_FILES_PATH_VARIABLE] = array(
256
+    '#type' => 'textfield',
257
+    '#title' => t('Directory for your site\'s files in your TuxFamily repository'),
258
+    '#description' => t('Which subdirectory within your TuxFamily repository should be used to store files uploaded to your website?'),
259
+    '#required' => TRUE,
260
+    '#default_value' => variable_get(TUXFAMILY_REPOSITORY_DRUPAL_FILES_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_FILES_PATH),
261
+  );
262
+  $form['tuxfamily_repository_options'][TUXFAMILY_REPOSITORY_DRUPAL_STATIC_PATH_VARIABLE] = array(
138 263
     '#type' => 'textfield',
139
-    '#title' => t('Drupal-dedicated directory in your TuxFamily repository'),
140
-    '#description' => t('Which subdirectory within your TuxFamily repository should be used to store Drupal files?'),
264
+    '#title' => t('Directory for Drupal static files in your TuxFamily repository'),
265
+    '#description' => t('Which subdirectory within your TuxFamily repository should be used to store static resources (CSS, JS, images, ...) provided by Drupal and its modules?'),
141 266
     '#required' => TRUE,
142
-    '#default_value' => variable_get(TUXFAMILY_REPOSITORY_DRUPAL_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH),
267
+    '#default_value' => variable_get(TUXFAMILY_REPOSITORY_DRUPAL_STATIC_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_STATIC_PATH),
268
+  );
269
+  $form['tuxfamily_repository_options']['tuxfamily_repository_sync_instructions'] = array(
270
+    '#markup' => sprintf(t('You may rsync Drupal static files on-demand using <a href="%s">%s</a>.'), url('admin/config/tuxfamily'), 'admin/config/tuxfamily'),
143 271
   );
144 272
 
145 273
   // advanced options
146 274
   $form['tuxfamily_repository_advanced_options'] = array(
147 275
     '#type' => 'fieldset',
148 276
     '#title' => 'TuxFamily repository advanced options',
149
-    '#description' => 'Unless TuxFamily has modified their architecture, you should not need to change these settings. In the pattern below, %1$s is your Tuxfamily project name while %2$s is the Drupal-dedicated directory in your TuxFamily repository.',
277
+    '#description' => 'Unless TuxFamily has modified their architecture, you should not need to change these settings.',
150 278
     '#collapsible' => TRUE,
151 279
     '#collapsed' => TRUE,
152 280
     '#weight' => -998,
153 281
   );
282
+  $form['tuxfamily_repository_advanced_options']['tokens'] = array(
283
+    '#markup' => 'The following tokens are available for paths/URLs: @sitename (e.g. default if you are not using a multisite setup), @drupalroot (path to the Drupal directory) and @project (your TuxFamily project short name), @path (path that will be appended, to be considered mandatory).',
284
+  );
154 285
   $form['tuxfamily_repository_advanced_options'][TUXFAMILY_REPOSITORY_BASE_URL_PATTERN_VARIABLE] = array(
155 286
     '#type' => 'textfield',
156 287
     '#title' => t('Base URL of your TuxFamily repository'),
... ...
@@ -165,6 +296,35 @@ function _tuxfamily_repository_alter_file_system_settings(&$form, &$form_state,
165 296
     '#required' => TRUE,
166 297
     '#default_value' => variable_get(TUXFAMILY_REPOSITORY_DRUPAL_PATH_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH_PATTERN),
167 298
   );
299
+  $form['tuxfamily_repository_advanced_options'][TUXFAMILY_REPOSITORY_RSYNC_PATH_VARIABLE] = array(
300
+    '#type' => 'textfield',
301
+    '#title' => t('Rsync command'),
302
+    '#description' => t('Path to the rsync tool.'),
303
+    '#required' => TRUE,
304
+    '#default_value' => variable_get(TUXFAMILY_REPOSITORY_RSYNC_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_RSYNC_PATH),
305
+  );
306
+}
307
+
308
+/**
309
+ * Describe the _tuxfamily_repository_config form, which allows administrators
310
+ * to rsync static files to their TuxFamily repository on-demand.
311
+ */
312
+function _tuxfamily_repository_config($form, &$form_state) {
313
+  $form['instructions'] = array(
314
+    '#markup' => t('This page allows you to manually rsync Drupal static files to your TuxFamily repository. Use it with caution since it does not implement a lock yet, which could lead to multiple executions of rsync at the same time.') . '<br /><br />',
315
+  );
316
+  $form['go'] = array(
317
+    '#type' => 'submit',
318
+    '#value' =>  'Go',
319
+  );
320
+  return $form;
321
+}
322
+
323
+/**
324
+ * Handle submission of the _tuxfamily_repository_config form.
325
+ */
326
+function _tuxfamily_repository_config_submit($form, &$form_state) {
327
+  _tuxfamily_repository_rsync_static_files();
168 328
 }
169 329
 
170 330
 /**
... ...
@@ -174,6 +334,136 @@ function _tuxfamily_repository_alter_file_system_settings(&$form, &$form_state,
174 334
 function _tuxfamily_repository_submit_file_system_settings($form, $form_state) {
175 335
   $tf_repos_enabled = variable_get(TUXFAMILY_REPOSITORY_ENABLED_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_ENABLED);
176 336
   if ($tf_repos_enabled) {
177
-    variable_set('file_public_path', _tuxfamily_repository_get_file_public_path());
337
+    variable_set('file_public_path', _tuxfamily_repository_get_files_base_path());
338
+  }
339
+}
340
+
341
+/**
342
+ * @return the list of directories that should be deported to the TuxFamily
343
+ * repository for this module to be efficient.
344
+ */
345
+function _tuxfamily_repository_deported_directories() {
346
+  return array('misc', 'modules', 'themes', 'sites/all');
347
+}
348
+
349
+/**
350
+ * Copy all directories returned by _tuxfamily_repository_deported_directories
351
+ * to the Tuxfamily repository using rsync.
352
+ * @return true if everything went ok, false otherwise.
353
+ */
354
+function _tuxfamily_repository_rsync_static_files() {
355
+  $tf_repos_enabled = variable_get(TUXFAMILY_REPOSITORY_ENABLED_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_ENABLED);
356
+  if (!$tf_repos_enabled) {
357
+    return FALSE;
358
+  }
359
+  if (!_tuxfamily_repository_check_rsync_presence()) {
360
+    drupal_set_message(t('Unable to find the rsync tool'), 'error');
361
+    return FALSE;
178 362
   }
363
+  foreach (array('misc', 'modules', 'themes', 'sites/all') as $subset) {
364
+    $exec = _tuxfamily_repository_rsync_subset($subset);
365
+    if ($exec['code'] !== 0) {
366
+      // avoid displaying too much information
367
+      $output = drupal_strlen($exec['output']) > 1024 ? drupal_substr(0, 1024, $exec['output']) : $exec['output'];
368
+      drupal_set_message(t('An error occured when syncing @subset to your TuxFamily download repository: @output', array('@subset' => $subset, '@output' => $exec['output'])), 'error');
369
+      return FALSE;
370
+    }
371
+  }
372
+  drupal_set_message(t('Static files have been rsync\'ed to your TuxFamily repository'));
373
+  return TRUE;
374
+}
375
+
376
+/**
377
+ * @return true if the rsync tool is present, false otherwise.
378
+ */
379
+function _tuxfamily_repository_check_rsync_presence() {
380
+  // this module simply expects to find the rsync tool in the PATH
381
+  return file_exists(variable_get(TUXFAMILY_REPOSITORY_RSYNC_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_RSYNC_PATH));
382
+}
383
+
384
+/**
385
+ * @return the exclude options to be used within rsync commands.
386
+ */
387
+function _tuxfamily_repository_exclude_options() {
388
+  $options = '';
389
+  foreach (array('php', 'inc', 'module', 'install', 'test', 'info') as $ext) {
390
+    $options .= sprintf(' --exclude=\'*.%s\'', $ext);
391
+  }
392
+  return $options;
393
+}
394
+
395
+/**
396
+ * Copy a given directory to the TuxFamily repository using rsync.
397
+ * @param $subset Subdirectory to be rsync'ed.
398
+ */
399
+function _tuxfamily_repository_rsync_subset($subset) {
400
+  $static_root = _tuxfamily_repository_get_static_base_path();
401
+  if (!is_dir($static_root)) {
402
+    if (!mkdir($static_root)) {
403
+      drupal_set_message(t('Unable to create directory @root.', array('@root' => $static_root)), 'error');
404
+      return FALSE;
405
+    }
406
+  }
407
+
408
+  $command = _tuxfamily_repository_apply_tokens(
409
+    '@rsync --delete --archive --quiet @exclude_options --relative @drupal_root/./@subset @repository_static_root',
410
+    array(
411
+      '@subset' => $subset,
412
+      '@repository_static_root' => $static_root,
413
+      '@exclude_options' => _tuxfamily_repository_exclude_options(),
414
+    )
415
+  );
416
+  drupal_set_time_limit(120);
417
+  return _tuxfamily_repository_execute_command($command);
418
+}
419
+
420
+/**
421
+ * @return the base name of the directory hosting this site's configuration
422
+ * file
423
+ */
424
+function _tuxfamily_repository_site_name() {
425
+  return basename(conf_path());
426
+}
427
+
428
+/**
429
+ * @return the base tokens available when interpreting paths configured for
430
+ * this module.
431
+ */
432
+function _tuxfamily_repository_default_tokens() {
433
+  return array(
434
+    '@drupal_root' => DRUPAL_ROOT,
435
+    '@sitename'    => _tuxfamily_repository_site_name(),
436
+    '@project'     => variable_get(TUXFAMILY_REPOSITORY_PROJECT_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_PROJECT),
437
+    '@rsync'       => variable_get(TUXFAMILY_REPOSITORY_RSYNC_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_RSYNC_PATH),
438
+  );
439
+}
440
+
441
+/**
442
+ * @param $string String to be interpreted; tokens from
443
+ * _tuxfamily_repository_default_tokens will be replaced
444
+ * @param $extra_tokens List of extra tokens that should be replaced in $string
445
+ * @return The string with all known and provided tokens replaced
446
+ */
447
+function _tuxfamily_repository_apply_tokens($string, $extra_tokens = array()) {
448
+  $tokens = _tuxfamily_repository_default_tokens() + $extra_tokens;
449
+
450
+  foreach ($tokens as $token => $value) {
451
+    $string = str_replace($token, $value, $string);
452
+  }
453
+
454
+  return $string;
455
+}
456
+
457
+/**
458
+ * @param $command Command to be executed
459
+ * $command_redirections input/output redirections applied to the command (see
460
+ * popen documentation); default to '< /dev/null 2>&1'.
461
+ */
462
+function _tuxfamily_repository_execute_command($command, $command_redirections = '< /dev/null 2>&1') {
463
+  $return = array('command' => $command, 'code' => -1, 'output' => '');
464
+  $proc_res = popen($command . ' ' . $command_redirections, 'r');
465
+  if ($proc_res === FALSE) return $return;
466
+  $return['output'] = stream_get_contents($proc_res);
467
+  $return['code'] = pclose($proc_res);
468
+  return $return;
179 469
 }