1+ import { IExtensionApi , IExtensionContext } from '../../types/IExtensionContext' ;
2+ import { IGame } from '../../types/IGame' ;
3+ import * as fs from '../../util/fs' ;
4+ import { log } from '../../util/log' ;
5+
6+ import { IDiscoveryResult } from '../gamemode_management/types/IDiscoveryResult' ;
7+ import { getGame } from '../gamemode_management/util/getGame' ;
8+ import LinkingDeployment from '../mod_management/LinkingDeployment' ;
9+ import { installPathForGame } from '../mod_management/selectors' ;
10+ import { IDeployedFile , IDeploymentMethod ,
11+ IUnavailableReason } from '../mod_management/types/IDeploymentMethod' ;
12+
13+ import Promise from 'bluebird' ;
14+ import { TFunction } from 'i18next' ;
15+ import * as path from 'path' ;
16+
17+ class DeploymentMethod extends LinkingDeployment {
18+ public priority : number = 1 ; // Highest priority to make it default
19+
20+ constructor ( api : IExtensionApi ) {
21+ super (
22+ 'copy_activator' , 'Copy Deployment' ,
23+ 'Deploys mods by copying files to the destination directory.' ,
24+ true ,
25+ api ) ;
26+ }
27+
28+ public detailedDescription ( t : TFunction ) : string {
29+ return t (
30+ 'This deployment method copies mod files directly to the game directory.\n'
31+ + 'Advantages:\n'
32+ + ' - Perfect game compatibility (no symlinks)\n'
33+ + ' - Works across different drives/partitions\n'
34+ + ' - No elevation required\n'
35+ + ' - Compatible with all file systems\n'
36+ + 'Disadvantages:\n'
37+ + ' - Uses more disk space (files are duplicated)\n'
38+ + ' - Slower deployment for large mods\n'
39+ + ' - Changes to original mod files won\'t be reflected automatically' ) ;
40+ }
41+
42+ public isSupported ( state : any , gameId : string , typeId : string ) : IUnavailableReason {
43+ const discovery : IDiscoveryResult = state . settings . gameMode . discovered [ gameId ] ;
44+ if ( ( discovery === undefined ) || ( discovery . path === undefined ) ) {
45+ return { description : t => t ( 'Game not discovered.' ) } ;
46+ }
47+
48+ const game : IGame = getGame ( gameId ) ;
49+ const modPaths = game . getModPaths ( discovery . path ) ;
50+
51+ if ( modPaths [ typeId ] === undefined ) {
52+ return undefined ;
53+ }
54+
55+ try {
56+ fs . accessSync ( modPaths [ typeId ] , fs . constants . W_OK ) ;
57+ } catch ( err ) {
58+ log ( 'info' , 'copy deployment not supported due to lack of write access' ,
59+ { typeId, path : modPaths [ typeId ] } ) ;
60+ return {
61+ description : t => t ( 'Can\'t write to output directory' ) ,
62+ order : 3 ,
63+ solution : t => t ( 'To resolve this problem, the current user account needs to '
64+ + 'be given write permission to "{{modPath}}".' , {
65+ replace : {
66+ modPath : modPaths [ typeId ] ,
67+ } ,
68+ } ) ,
69+ } ;
70+ }
71+
72+ return undefined ;
73+ }
74+
75+ protected linkFile ( linkPath : string , sourcePath : string , dirTags ?: boolean ) : Promise < void > {
76+ const basePath = path . dirname ( linkPath ) ;
77+ return this . ensureDir ( basePath , dirTags )
78+ . then ( ( ) => fs . copyAsync ( sourcePath , linkPath ) )
79+ . catch ( err => {
80+ if ( err . code === 'EEXIST' ) {
81+ // File already exists, remove it and try again
82+ return fs . removeAsync ( linkPath )
83+ . then ( ( ) => fs . copyAsync ( sourcePath , linkPath ) ) ;
84+ }
85+ return Promise . reject ( err ) ;
86+ } ) ;
87+ }
88+
89+ protected unlinkFile ( linkPath : string ) : Promise < void > {
90+ return fs . removeAsync ( linkPath ) ;
91+ }
92+
93+ protected isLink ( linkPath : string , sourcePath : string ) : Promise < boolean > {
94+ // For copy deployment, we check if the file exists and has the same content
95+ return Promise . all ( [
96+ fs . statAsync ( linkPath ) . catch ( ( ) => null ) ,
97+ fs . statAsync ( sourcePath ) . catch ( ( ) => null )
98+ ] )
99+ . then ( ( [ linkStats , sourceStats ] ) => {
100+ if ( ! linkStats || ! sourceStats ) {
101+ return false ;
102+ }
103+ // Simple check: same size and modification time
104+ return linkStats . size === sourceStats . size ;
105+ } ) ;
106+ }
107+
108+ protected canRestore ( ) : boolean {
109+ return true ;
110+ }
111+
112+ protected stat ( filePath : string ) : Promise < fs . Stats > {
113+ return fs . statAsync ( filePath ) ;
114+ }
115+
116+ protected statLink ( filePath : string ) : Promise < fs . Stats > {
117+ return fs . statAsync ( filePath ) ;
118+ }
119+ }
120+
121+ export interface IExtensionContextEx extends IExtensionContext {
122+ registerDeploymentMethod : ( activator : IDeploymentMethod ) => void ;
123+ }
124+
125+ function init ( context : IExtensionContextEx ) : boolean {
126+ context . registerDeploymentMethod ( new DeploymentMethod ( context . api ) ) ;
127+ return true ;
128+ }
129+
130+ export default init ;
0 commit comments