siedZddlZddlZddlZddlmZddlmZddlmZm Z m Z m Z ddl m Z ddlmZedZGd d ZdS) zq Version Manager - Handles project versioning operations Supports upload, download, restore of project snapshots N)Path)datetime)DictAnyOptionalList)logger)Databasez'I:/SOFTWARE/PYTHON/DEVELOPMENT/versionsc deZdZdZdefdZ ddededed eee ffd Z dd ed ed eee ffd Z dd ed eee ffdZ d efdZ ddededeed eee ffdZdded ed eee ffdZded efdZ ddededeed eee ffdZdedeed efdZdS)VersionManagerz8Manages project version uploads, downloads, and restoresdbc||_dSN)r )selfr s +C:\PYTHON\Scheduler\core\version_manager.py__init__zVersionManager.__init__s N project_id version_name descriptionreturnc j |j|}|s dd|ddSt|d}t|d}|dr|ddng}|sdd |dSt |d z } ||d z } | d d |j|} tfd | Dr ddddStj d|d ddddl } | } tj d|d| | s/| d d tj d| ntj d| fd}| || ||}tj d|ddtj dddl }tjd}d|d }| |z }t|d!z }|d "||z }fd#}|||||}tj d$|d%d&z d'd(tj d)|t'j|||tj d*|dd+|j||||d%|d,}| | z }t/|d-z}t/|d-z}|dkr|d.|d/n|d/}tj d0|d1|dd2|d%d&z d'd3|d ||d|d%t1|t1| |dd4d5S#t2$r6}tjd6|dd7t1|dcYd}~Sd}~wwxYw)8a Upload new version: 1. Get project config 2. Create ZIP snapshot from source 3. Extract ZIP to server active location (for direct execution with Python embedded) 4. Save metadata Args: project_id: Project configuration ID version_name: Version identifier (e.g., "v1.2.0") description: Optional version description Returns: Dict with stats: { 'success': bool, 'files_count': int, 'size_bytes': int, 'zip_path': str, 'server_path': str, 'message': str } FProject not foundsuccessmessage source_pathserver_base_pathexclude_patterns zSource path not found: nameTparentsexist_okc30K|]}|dkVdS)rN).0vrs r z0VersionManager.upload_version..Gs-PP1^$ 4PPPPPPr Version 'z' already existszStarting upload for project 'z ' version ''rNzCopying files from  to zCreated new directory: zUsing existing directory: c,rd|||dSdS)Ncopyr(currenttotalfilenameprogress_callbacks r copy_progressz4VersionManager.upload_version..copy_progress\s5$H%%fguhGGGGGHHrzCopied files_countz files to serverzCreating local ZIP snapshot %Y%m%d_%H%M%S_.zipscheduler_zips)r&c,rd|||dSdS)Nzipr(r1s r zip_progressz3VersionManager.upload_version..zip_progressrs5$G%%eWeXFFFFFGGrzZIP created locally: size_bytesiz.2fz MBzCopying ZIP to server: zUpload complete:  filesrr zip_filenamerr?r7<zm sVersion z created successfully: z files, z MB in z"' uploaded and extracted to server)r version_idr7r?zip_path server_path elapsed_timerzError uploading version: zUpload failed: )r get_project_configrsplitexistsVERSIONS_BASE_PATHmkdirget_project_versionsanyr infotime _copy_treetempfilernowstrftime gettempdir _create_zipshutilcopy2unlinkcreate_project_versionintstr Exceptionerror)rrrrr5projectr server_baser! versions_dir server_activeexisting_versionsrR start_timer6 copy_statsrT timestamprBrGtemp_dirtemp_zipr> zip_statsrFrIelapsed_minuteselapsed_secondstime_stres ` ` rupload_versionzVersionManager.upload_versions0m Mg00<OPPPPP a#(5_5_5_5_``` KcccT`ccc d d d KKKJ KNkNN}NN O O O!'')) J##D4#@@@ EmEEFFFF HHHIII H H H H H mEUWdeeJ KM*]";MMM N N N K6 7 7 7 OOO //@@I*<.extract_progress5$K%%i%JJJJJKKrzVersion downloaded: r7r@r,z' downloaded successfully)rr7rqrzError downloading version: zDownload failed: N) r get_project_versionrJrMrLrrNr rQ _extract_zipr^r_r`) rrFrqr5versionrarcrG dest_pathrw extract_statsros ` rdownload_versionzVersionManager.download_versions * Og11*==G X#(5V 5V5V5VWWWg001FGGG c#(5a @U5a5a5abbb.?L#gn&==H??$$ X#(5VH5V5VWWW[))I OOD4O 8 8 8 KY0GYYiYY Z Z Z K K K K K!--h CSTTM KS}]/KSSS T T T ,];"9~~Yw~'>YYY   O O O L:q:: ; ; ;$1MSVV1M1MNN N N N N N N Os/$D40D46D4B#D44 E4>+E/)E4/E4c  |j|}|s dd|ddS|j|d}|sdd|dddSt|d}||dz }t|dz }||d z }|sdd |dSt jd |d d |ddd} d} |rt| rtj d} d| } d|d } t jd|dr|d dng} | d}||z }fd}|||| |}|j|d| || |d|d} t jd| nt jd|rkt jd|| D]?}|rt#j|+|@t jd|d d||d d !fd"}||||}t jd#|dd$d%|d d&}| r |d'| dz }n|d(z }d |d| | |d)S#t,$r6}t jd*|dd+t1|dcYd}~Sd}~wwxYw),ah Restore version to server active location Creates automatic backup first Args: version_id: Version record ID Returns: Dict with stats: { 'success': bool, 'files_count': int, 'backup_version_id': int, 'message': str } FrErrrrr r#rBrszRestoring version 'rz' for project 'r-Nr8zpre-restore-backup-zAuto-backup before restoring zCreating pre-restore backup...r!r"r:c,rd|||dSdS)Nbackupr(r1s rbackup_progressz7VersionManager.restore_version..backup_progresss5(N))(GUHMMMMMNNrr?r7rAz'Pre-restore backup created: version ID z+No existing files to backup (first restore)zClearing existing files in z Extracting r.Tr$c,rd|||dSdSrur(r1s rrwz8VersionManager.restore_version..extract_progress6rxrzVersion restored: r@r,z' restored successfullyz. Backup created as 'z" (first restore, no backup needed))rr7backup_version_idbackup_version_namerzError restoring version: zRestore failed: )r ryrJrrMrLr rQrPiterdirrrUrVrKrXr\is_dirrYrmtreer[rNrzr_r`r^)rrFr5r{rarbrdrcrGrrrhbackup_descriptionr!backup_zip_filenamebackup_zip_pathr backup_statsitemrwr}rros ` rrestore_versionzVersionManager.restore_versions b Ng11*==G X#(5V 5V5V5VWWWg001FGGG c#(5a @U5a5a5abbbw'9:;;K''&/9M-?L#gn&==H??$$ X#(5VH5V5VWWW Khgn.EhhV]^dVehhh i i i!% "& ##%% K#m.C.C.E.E*F*F K$LNN33ODD &GI&G&G#%^W^E\%^%^" <===NUVhNi#q7+=#>#D#DT#J#J#Joq *=&B&B&B#".1D"DNNNNN $// P`bqrr %)G$B$B&|4!4!4 2+L9 ,] ; %C%%! YFWYYZZZZ IJJJ##%% & I-IIJJJ)1133&&D{{}}& d++++  KRgn&=RR=RR S S S   t  < < < K K K K K!--h GWXXM KQ]=-IQQQ R R RS'."9RRRG" @I3FIIII?? ,];%6':"   N N N L8Q88 9 9 9$1LCFF1L1LMM M M M M M M Ns0$L 0L AL 0I/L M *+MM M cl |j|}|s dd|ddS|j|d}|sdd|dddSt|dz }||dz }|r,|t jd |nt jd ||j |d d |d ddS#t$r6}t j d|ddt|dcYd}~Sd}~wwxYw)z Delete version ZIP and database record Args: version_id: Version record ID Returns: Dict with result: { 'success': bool, 'message': str } FrErrrrr#rBzDeleted ZIP file: rsTr,rz' deleted successfullyzError deleting version: zDelete failed: N) r ryrJrMrLr[r rQwarningdelete_project_versionr_r`r^)rrFr{rarcrGros rdelete_versionzVersionManager.delete_versionQs Mg11*==G X#(5V 5V5V5VWWWg001FGGG c#(5a @U5a5a5abbb.?L#gn&==H   B!!! ;;;<<<<@h@@AAA G * *: 6 6 6 Vw~'>VVV   M M M L7A77 8 8 8$1K3q661K1KLL L L L L L L Ms)$C30C3BC33 D3=+D.(D3.D3 source_dirrGr!c Bddl}d}|rtj|D]\}}} t|} | |fd|D|dd<| D]D} | | z } | |} t | s|dz }Ed}d}tj|dtj 5}tj|D]\}}} t|} | |fd|D|dd<| D]} | | z } | |} t | r@ | | | |dz }|| j z }|r|||t | #t$r$}tjd| d|Yd}~d}~wwxYw dddn #1swxYwY| j }|||d S) a Helper: Create ZIP with exclusions Args: source_dir: Directory to compress zip_path: Output ZIP file path exclude_patterns: Patterns to exclude progress_callback: Optional function(current, total, filename) for progress updates Returns: Dict with stats: { 'files_count': int, 'size_bytes': int } rNc`g|]*}t|z (|+Sr(_should_excluder^r)dr!rel_rootrs r z.VersionManager._create_zip..<lll$2F2Fs8VW.rrzFailed to add z to ZIP: )r7r?uncompressed_size)fnmatchoswalkr relative_torr^zipfileZipFile ZIP_DEFLATEDwritestatst_sizer_r r)rrrGr!r5r total_filesrootdirsfiles root_pathfile file_pathrel_pathr7r?zipfrozip_sizers` ` @rrXzVersionManager._create_zips"   )%'WZ%8%8 ) )!dE JJ $00<<mllllldlllQQQ!))D )D 0I(44Z@@H//H ?OPP)#q( )   _XsG,@ A A PT%'WZ%8%8 P P!dE JJ $00<<mllllldlllQQQ!PPD )D 0I(44Z@@H++CMM;KLL!  P 9h777#q( "inn&6&6&>> -W--k;H VVV$PPP'N'N'N1'N'NOOOOOOOOP!P P P P P P P P P P P P P P P P P:==??*'"!+   s> BG7#AF76G77 G% G G7 G% %G77G;>G;c2d}tj|d5}|}t|}t |dD]/\}} || ||dz }|r |||| 0 dddn #1swxYwYd|iS)a[ Helper: Extract ZIP to destination Args: zip_path: Source ZIP file destination: Extraction destination progress_callback: Optional function(current, total, filename) for progress updates Returns: Dict with stats: { 'files_count': int } rrr)startNr7)rrnamelistlen enumeraterv) rrGrqr5r7rmembersrindexmembers rrzzVersionManager._extract_zips  _Xs + + BtmmooGg,,K!*7!!auxconnulprncom1com2com3com4com5com6com7com8com9lpt1lpt2lpt3lpt4lpt5lpt6lpt7lpt8lpt9.r)lowerrK)rr4reserved_names name_lower base_names r_is_windows_device_namez&VersionManager._is_windows_device_namesG     ^^%% $$S))!, N**rdest_dircddl}tjd|d|gdt|t|dddd d d d d ddddddddddddddddddd d!d"d#d$d%d&d'd(d)d*d+d,}tjd-|r |ddd.tjd/ ||d0d0d1d23}n3#|j$r&d4}tj|t|wxYw|jd5kred6|j}tj|tjd7|j tjd8|j t|tjd9|jtjd:|r |d;d;d<ddd=S)>ag Helper: Copy directory tree using Robocopy + Python cleanup Strategy: 1. Use Robocopy for fast bulk copy (all files, multi-threaded) 2. Use Python to remove excluded files/directories This is much faster than file-by-file copy, especially on network drives. With Python embedded, exclusions are minimal (~1% of files), making this approach optimal: Robocopy copies ~99%, Python cleanup removes ~1%. Args: source_dir: Source directory to copy from dest_dir: Destination directory to copy to exclude_patterns: Patterns to exclude progress_callback: Optional function(current, total, filename) for progress updates Returns: Dict with stats: { 'files_count': int, 'bytes_copied': int } rNz Robocopy: z -> robocopyz/MIRz/MT:16z/R:2z/W:1z/NFLz/NDLz/NPz/BYTESz/NCz/NSz/NJHz/NJSz/XFrrrrznul.*zcon.*zprn.*zaux.*rrrrrrrrrrrrrrrrrrz@Robocopy will copy all files (no pre-counting for maximum speed)z"Multi-threaded copy in progress...zStarting Robocopy execution...Tcp1252i)capture_outputtextencodingtimeoutz#Robocopy timed out after 15 minuteszRobocopy failed with exit code zRobocopy stdout: zRobocopy stderr: z/Robocopy completed successfully with exit code z4'>BB gooV^adVdfmFnFn 44 s++s5zz**$$AuQx#~w??$#ttt$$ ??8TG^<< PXZa@b@b 44#>>#..r2??8W55 44 ur)rNr)__name__ __module__ __qualname____doc__r rr]r^rrrpr~rrrrrXrzboolrrSrr(rrr r slBB8@DEMEMEMCEM#&EMHLSRUXEMEMEMEMN:O:O3:OS:O]abegjbj]k:O:O:O:OxrNrN#rN$sTWx.rNrNrNrNh-M-M-M-M-M`CGI I dI dI %)#YI KOPSUXPX>I I I I V  T  Y]^acf^fYg    @ + + + + + + CGd d Td Td %)#Yd KOPSUXPX>d d d d L--tCy-T------rr )rrrrYpathlibrrtypingrrrrlogurur core.databaser rMr r(rrrs   ,,,,,,,,,,,,""""""TCDDy y y y y y y y y y r