123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- /**
- * Board Migration Detector
- * Detects boards that need migration and manages automatic migration scheduling
- */
- import { Meteor } from 'meteor/meteor';
- import { ReactiveVar } from 'meteor/reactive-var';
- import { cronJobStorage } from './cronJobStorage';
- // Reactive variables for board migration tracking
- export const unmigratedBoards = new ReactiveVar([]);
- export const migrationScanInProgress = new ReactiveVar(false);
- export const lastMigrationScan = new ReactiveVar(null);
- class BoardMigrationDetector {
- constructor() {
- this.scanInterval = null;
- this.isScanning = false;
- this.migrationCheckInterval = 30000; // Check every 30 seconds
- this.scanInterval = 60000; // Full scan every minute
- }
- /**
- * Start the automatic migration detector
- */
- start() {
- if (this.scanInterval) {
- return; // Already running
- }
- // Check for idle migration opportunities
- this.scanInterval = Meteor.setInterval(() => {
- this.checkForIdleMigration();
- }, this.migrationCheckInterval);
- // Full board scan every minute
- this.fullScanInterval = Meteor.setInterval(() => {
- this.scanUnmigratedBoards();
- }, this.scanInterval);
- console.log('Board migration detector started');
- }
- /**
- * Stop the automatic migration detector
- */
- stop() {
- if (this.scanInterval) {
- Meteor.clearInterval(this.scanInterval);
- this.scanInterval = null;
- }
- if (this.fullScanInterval) {
- Meteor.clearInterval(this.fullScanInterval);
- this.fullScanInterval = null;
- }
- }
- /**
- * Check if system is idle and can run migrations
- */
- isSystemIdle() {
- const resources = cronJobStorage.getSystemResources();
- const queueStats = cronJobStorage.getQueueStats();
-
- // Check if no jobs are running
- if (queueStats.running > 0) {
- return false;
- }
- // Check if CPU usage is low
- if (resources.cpuUsage > 30) { // Lower threshold for idle migration
- return false;
- }
- // Check if memory usage is reasonable
- if (resources.memoryUsage > 70) {
- return false;
- }
- return true;
- }
- /**
- * Check for idle migration opportunities
- */
- async checkForIdleMigration() {
- if (!this.isSystemIdle()) {
- return;
- }
- // Get unmigrated boards
- const unmigrated = unmigratedBoards.get();
- if (unmigrated.length === 0) {
- return; // No boards to migrate
- }
- // Check if we can start a new job
- const canStart = cronJobStorage.canStartNewJob();
- if (!canStart.canStart) {
- return;
- }
- // Start migrating the next board
- const boardToMigrate = unmigrated[0];
- await this.startBoardMigration(boardToMigrate);
- }
- /**
- * Scan for unmigrated boards
- */
- async scanUnmigratedBoards() {
- if (this.isScanning) {
- return; // Already scanning
- }
- this.isScanning = true;
- migrationScanInProgress.set(true);
- try {
- console.log('Scanning for unmigrated boards...');
-
- // Get all boards from the database
- const boards = this.getAllBoards();
- const unmigrated = [];
- for (const board of boards) {
- if (await this.needsMigration(board)) {
- unmigrated.push(board);
- }
- }
- unmigratedBoards.set(unmigrated);
- lastMigrationScan.set(new Date());
- console.log(`Found ${unmigrated.length} unmigrated boards`);
- } catch (error) {
- console.error('Error scanning for unmigrated boards:', error);
- } finally {
- this.isScanning = false;
- migrationScanInProgress.set(false);
- }
- }
- /**
- * Get all boards from the database
- */
- getAllBoards() {
- // This would need to be implemented based on your board model
- // For now, we'll simulate getting boards
- try {
- // Assuming you have a Boards collection
- if (typeof Boards !== 'undefined') {
- return Boards.find({}, { fields: { _id: 1, title: 1, createdAt: 1, modifiedAt: 1 } }).fetch();
- }
-
- // Fallback: return empty array if Boards collection not available
- return [];
- } catch (error) {
- console.error('Error getting boards:', error);
- return [];
- }
- }
- /**
- * Check if a board needs migration
- */
- async needsMigration(board) {
- try {
- // Check if board has been migrated by looking for migration markers
- const migrationMarkers = this.getMigrationMarkers(board._id);
-
- // Check for specific migration indicators
- const needsListMigration = !migrationMarkers.listsMigrated;
- const needsAttachmentMigration = !migrationMarkers.attachmentsMigrated;
- const needsSwimlaneMigration = !migrationMarkers.swimlanesMigrated;
-
- return needsListMigration || needsAttachmentMigration || needsSwimlaneMigration;
-
- } catch (error) {
- console.error(`Error checking migration status for board ${board._id}:`, error);
- return false;
- }
- }
- /**
- * Get migration markers for a board
- */
- getMigrationMarkers(boardId) {
- try {
- // Check if board has migration metadata
- const board = Boards.findOne(boardId, { fields: { migrationMarkers: 1 } });
-
- if (!board || !board.migrationMarkers) {
- return {
- listsMigrated: false,
- attachmentsMigrated: false,
- swimlanesMigrated: false
- };
- }
- return board.migrationMarkers;
- } catch (error) {
- console.error(`Error getting migration markers for board ${boardId}:`, error);
- return {
- listsMigrated: false,
- attachmentsMigrated: false,
- swimlanesMigrated: false
- };
- }
- }
- /**
- * Start migration for a specific board
- */
- async startBoardMigration(board) {
- try {
- console.log(`Starting migration for board: ${board.title || board._id}`);
-
- // Create migration job for this board
- const jobId = `board_migration_${board._id}_${Date.now()}`;
-
- // Add to job queue with high priority
- cronJobStorage.addToQueue(jobId, 'board_migration', 1, {
- boardId: board._id,
- boardTitle: board.title,
- migrationType: 'full_board_migration'
- });
- // Save initial job status
- cronJobStorage.saveJobStatus(jobId, {
- jobType: 'board_migration',
- status: 'pending',
- progress: 0,
- boardId: board._id,
- boardTitle: board.title,
- migrationType: 'full_board_migration',
- createdAt: new Date()
- });
- // Remove from unmigrated list
- const currentUnmigrated = unmigratedBoards.get();
- const updatedUnmigrated = currentUnmigrated.filter(b => b._id !== board._id);
- unmigratedBoards.set(updatedUnmigrated);
- return jobId;
- } catch (error) {
- console.error(`Error starting migration for board ${board._id}:`, error);
- throw error;
- }
- }
- /**
- * Get migration statistics
- */
- getMigrationStats() {
- const unmigrated = unmigratedBoards.get();
- const lastScan = lastMigrationScan.get();
- const isScanning = migrationScanInProgress.get();
- return {
- unmigratedCount: unmigrated.length,
- lastScanTime: lastScan,
- isScanning,
- nextScanIn: this.scanInterval ? this.scanInterval / 1000 : 0
- };
- }
- /**
- * Force a full scan of all boards
- */
- async forceScan() {
- console.log('Forcing full board migration scan...');
- await this.scanUnmigratedBoards();
- }
- /**
- * Get detailed migration status for a specific board
- */
- getBoardMigrationStatus(boardId) {
- const unmigrated = unmigratedBoards.get();
- const isUnmigrated = unmigrated.some(b => b._id === boardId);
-
- if (!isUnmigrated) {
- return { needsMigration: false, reason: 'Board is already migrated' };
- }
- const migrationMarkers = this.getMigrationMarkers(boardId);
- const needsMigration = !migrationMarkers.listsMigrated ||
- !migrationMarkers.attachmentsMigrated ||
- !migrationMarkers.swimlanesMigrated;
- return {
- needsMigration,
- migrationMarkers,
- reason: needsMigration ? 'Board requires migration' : 'Board is up to date'
- };
- }
- /**
- * Mark a board as migrated
- */
- markBoardAsMigrated(boardId, migrationType) {
- try {
- // Update migration markers
- const updateQuery = {};
- updateQuery[`migrationMarkers.${migrationType}Migrated`] = true;
- updateQuery['migrationMarkers.lastMigration'] = new Date();
- Boards.update(boardId, { $set: updateQuery });
- // Remove from unmigrated list if present
- const currentUnmigrated = unmigratedBoards.get();
- const updatedUnmigrated = currentUnmigrated.filter(b => b._id !== boardId);
- unmigratedBoards.set(updatedUnmigrated);
- console.log(`Marked board ${boardId} as migrated for ${migrationType}`);
- } catch (error) {
- console.error(`Error marking board ${boardId} as migrated:`, error);
- }
- }
- }
- // Export singleton instance
- export const boardMigrationDetector = new BoardMigrationDetector();
- // Start the detector on server startup
- Meteor.startup(() => {
- // Wait a bit for the system to initialize
- Meteor.setTimeout(() => {
- boardMigrationDetector.start();
- }, 10000); // Start after 10 seconds
- });
- // Meteor methods for client access
- Meteor.methods({
- 'boardMigration.getStats'() {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return boardMigrationDetector.getMigrationStats();
- },
- 'boardMigration.forceScan'() {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return boardMigrationDetector.forceScan();
- },
- 'boardMigration.getBoardStatus'(boardId) {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return boardMigrationDetector.getBoardMigrationStatus(boardId);
- },
- 'boardMigration.markAsMigrated'(boardId, migrationType) {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return boardMigrationDetector.markBoardAsMigrated(boardId, migrationType);
- }
- });
|