public static function importTree($sourcePath, $destinationPath, $options = array()) { $options = array_merge(array('exclude' => array(), 'delete' => true, 'debug' => false), $options); // backwords compatibility if (isset($options['transferDelete'])) { $options['delete'] = (bool) $options['transferDelete']; unset($options['transferDelete']); } // check source if (!is_readable($sourcePath)) { throw new Exception("Source \"{$sourcePath}\" unreadable"); } if (!empty($options['exclude']) && is_string($options['exclude'])) { $options['exclude'] = array($options['exclude']); } // normalize input paths $sourcePath = rtrim($sourcePath, '/'); if (!$destinationPath || $destinationPath == '/') { $destinationPath = null; } else { $destinationPath = trim($sourcePath, '/'); } // initialize state $prefixLen = strlen($sourcePath); $collectionsAnalyzed = 0; $collectionsDeleted = 0; $filesAnalyzed = 0; $filesExcluded = 0; $filesUpdated = 0; $filesDeleted = 0; // get complete list of directories existing in destination, build map of local collections $destinationCollectionsTree = static::getTree($destinationPath); $localDestinationCollectionsMap = array(); foreach ($destinationCollectionsTree as &$collectionInfo) { if ($collectionInfo['Site'] != 'Local') { continue; } if ($collectionInfo['ParentID'] && isset($destinationCollectionsTree[$collectionInfo['ParentID']])) { $collectionInfo['_path'] = $destinationCollectionsTree[$collectionInfo['ParentID']]['_path'] . '/' . $collectionInfo['Handle']; $localDestinationCollectionsMap[$collectionInfo['_path']] =& $collectionInfo; } elseif (!$collectionInfo['ParentID']) { $collectionInfo['_path'] = $collectionInfo['Handle']; } else { $collectionInfo['_path'] = $destinationPath; } } // get complete list of files existing in destination, build map of all by path $destinationFilesMap = static::getTreeFilesFromTree($destinationCollectionsTree); // configure iterator $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($sourcePath, FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST); // iterate through all source files foreach ($iterator as $tmpPath => $node) { $relPath = substr($tmpPath, $prefixLen); $path = $destinationPath ? $destinationPath . $relPath : ltrim($relPath, '/'); if ($options['debug']) { Debug::dump(array('tmpPath' => $tmpPath, 'destPath' => $path), false, 'iterating node'); } if (static::matchesExclude($relPath, $options['exclude'])) { $filesExcluded++; continue; } // handle directory if ($node->isDir()) { SiteCollection::getOrCreatePath($path); $collectionsAnalyzed++; // erase from destination map unset($localDestinationCollectionsMap[$path]); continue; } else { $filesAnalyzed++; } $existingNode = isset($destinationFilesMap[$path]) ? $destinationFilesMap[$path] : null; // calculate hash for incoming file $sha1 = sha1_file($node->getRealPath()); // skip if existing local or remote file matches hash if (!$existingNode || $existingNode['SHA1'] != $sha1) { if ($options['debug']) { print "Found SHA1 mismatch {$existingNode['SHA1']} != {$sha1}<br>"; } // use lower level create methods to supply already-calculated hash $fileRecord = SiteFile::createFromPath($path, null, $existingNode['ID']); SiteFile::saveRecordData($fileRecord, fopen($node->getPathname(), 'r'), $sha1); $filesUpdated++; } elseif ($options['debug']) { print "Skipping matching SHA1 to existing file<br>"; } // remove from dest files map if ($existingNode) { unset($destinationFilesMap[$path]); } } if ($options['delete']) { // delete local collections foreach ($localDestinationCollectionsMap as $path => $collectionInfo) { $relPath = substr($path, strlen($destinationPath)); // skip excluded paths if (static::matchesExclude($relPath, $options['exclude'])) { $filesExcluded++; continue; } DB::nonQuery('UPDATE `%s` SET Status = "Deleted" WHERE ID = %u', array(SiteCollection::$tableName, $collectionInfo['ID'])); $collectionsDeleted++; # print("Deleted collection $collectionInfo[ID] at $path<br>"); } // delete files foreach ($destinationFilesMap as $path => $fileInfo) { // skip remote files if ($fileInfo['Site'] != 'Local') { continue; } $relPath = substr($path, strlen($destinationPath)); // skip excluded paths if (static::matchesExclude($relPath, $options['exclude'])) { $filesExcluded++; continue; } DB::nonQuery('INSERT INTO `%s` SET CollectionID = %u, Handle = "%s", Status = "Deleted", AuthorID = %u, AncestorID = %u', array(SiteFile::$tableName, $fileInfo['CollectionID'], basename($path), !empty($GLOBALS['Session']) ? $GLOBALS['Session']->PersonID : null, $fileInfo['ID'])); $filesDeleted++; # print("Deleted file $fileInfo[ID] at $path<br>"); } } return array('collectionsAnalyzed' => $collectionsAnalyzed, 'collectionsDeleted' => $collectionsDeleted, 'filesAnalyzed' => $filesAnalyzed, 'filesExcluded' => $filesExcluded, 'filesUpdated' => $filesUpdated, 'filesDeleted' => $filesDeleted); }
foreach ($repoCfg['trees'] as $srcPath => $treeOptions) { if (is_string($treeOptions)) { $treeOptions = array('path' => $treeOptions); } if (!is_string($srcPath)) { $srcPath = $treeOptions['path']; } elseif (!$treeOptions['path']) { $treeOptions['path'] = $srcPath; } $treeOptions['exclude'][] = '#(^|/)\\.git(/|$)#'; if (is_file($treeOptions['path'])) { $sha1 = sha1_file($treeOptions['path']); $existingNode = Site::resolvePath($srcPath); if (!$existingNode || $existingNode->SHA1 != $sha1) { $fileRecord = SiteFile::createFromPath($srcPath, null, $existingNode ? $existingNode->ID : null); SiteFile::saveRecordData($fileRecord, fopen($treeOptions['path'], 'r'), $sha1); Benchmark::mark("importing file {$srcPath} from {$treeOptions['path']}"); } else { Benchmark::mark("skipped unchanged file {$srcPath} from {$treeOptions['path']}"); } } else { $cachedFiles = Emergence_FS::cacheTree($srcPath); Benchmark::mark("precached {$srcPath}: " . $cachedFiles); $exportResult = Emergence_FS::importTree($treeOptions['path'], $srcPath, $treeOptions); Benchmark::mark("importing directory {$srcPath} from {$treeOptions['path']}: " . http_build_query($exportResult)); } } // commit changes #$repo->git('add --all'); # #$repo->git(sprintf(
public static function handleImportRequest() { // get repo if (empty($_REQUEST['repo'])) { die('Parameter "repo" required'); } $repoName = $_REQUEST['repo']; if (!array_key_exists($repoName, Git::$repositories)) { die("Repo '{$repoName}' is not defined in Git::\$repositories"); } $repoCfg = Git::$repositories[$repoName]; // start the process set_time_limit(0); Benchmark::startLive(); Benchmark::mark("configured request: repoName={$repoName}"); // get paths $repoPath = "{$_SERVER['SITE_ROOT']}/site-data/git/{$repoName}"; // check if there is an existing repo if (!is_dir("{$repoPath}/.git")) { die("{$repoPath} does not contain .git"); } // get repo chdir($repoPath); // sync trees foreach ($repoCfg['trees'] as $srcPath => $treeOptions) { if (is_string($treeOptions)) { $treeOptions = array('path' => $treeOptions); } if (!is_string($srcPath)) { $srcPath = $treeOptions['path']; } elseif (!$treeOptions['path']) { $treeOptions['path'] = $srcPath; } if (is_string($treeOptions['exclude'])) { $treeOptions['exclude'] = array($treeOptions['exclude']); } $treeOptions['exclude'][] = '#(^|/)\\.git(/|$)#'; $treeOptions['dataPath'] = false; try { if (is_file($treeOptions['path'])) { $sha1 = sha1_file($treeOptions['path']); $existingNode = Site::resolvePath($srcPath); if (!$existingNode || $existingNode->SHA1 != $sha1) { $fileRecord = SiteFile::createFromPath($srcPath, null, $existingNode ? $existingNode->ID : null); SiteFile::saveRecordData($fileRecord, fopen($treeOptions['path'], 'r'), $sha1); Benchmark::mark("importing file {$srcPath} from {$treeOptions['path']}"); } else { Benchmark::mark("skipped unchanged file {$srcPath} from {$treeOptions['path']}"); } } else { $exportResult = Emergence_FS::importTree($treeOptions['path'], $srcPath, $treeOptions); Benchmark::mark("importing directory {$srcPath} from {$treeOptions['path']}: " . http_build_query($exportResult)); } } catch (Exception $e) { Benchmark::mark("failed to import directory {$srcPath} from {$treeOptions['path']}: " . $e->getMessage()); } } }