1010 */
1111
1212use Exception ;
13+ use Psr \EventDispatcher \EventDispatcherInterface ;
1314use Symfony \Component \Console \Command \Command ;
1415use Symfony \Component \Console \Style \SymfonyStyle ;
16+ use TYPO3 \CMS \Core \Resource \Driver \DriverInterface ;
17+ use TYPO3 \CMS \Core \Resource \Event \AfterFileAddedEvent ;
18+ use TYPO3 \CMS \Core \Resource \Event \AfterFileMovedEvent ;
19+ use TYPO3 \CMS \Core \Resource \Index \Indexer ;
1520use TYPO3 \CMS \Core \Resource \ResourceStorage ;
1621use TYPO3 \CMS \Core \Utility \PathUtility ;
1722use Visol \Cloudinary \Services \FileMoveService ;
@@ -31,13 +36,13 @@ class CloudinaryMoveCommand extends AbstractCloudinaryCommand
3136
3237 protected array $ missingFiles = [];
3338
34- protected ResourceStorage $ sourceStorage ;
35-
36- protected ResourceStorage $ targetStorage ;
39+ public function __construct (
40+ protected ResourceFactory $ resourceFactory ,
41+ protected EventDispatcherInterface $ eventDispatcher ,
42+ ) {
43+ parent ::__construct ();
44+ }
3745
38- /**
39- * Configure the command by defining the name, options and arguments
40- */
4146 protected function configure (): void
4247 {
4348 $ message = 'Move bunch of images to a cloudinary storage. Consult the README.md for more info. ' ;
@@ -60,34 +65,38 @@ protected function initialize(InputInterface $input, OutputInterface $output): v
6065 $ this ->io = new SymfonyStyle ($ input , $ output );
6166
6267 $ this ->isSilent = $ input ->getOption ('silent ' );
63-
64- /** @var ResourceFactory $resourceFactory */
65- $ resourceFactory = GeneralUtility::makeInstance (ResourceFactory::class);
66-
67- $ this ->sourceStorage = $ resourceFactory ->getStorageObject ($ input ->getArgument ('source ' ));
68- $ this ->targetStorage = $ resourceFactory ->getStorageObject ($ input ->getArgument ('target ' ));
6968 }
7069
7170 protected function execute (InputInterface $ input , OutputInterface $ output ): int
7271 {
73- if (!$ this ->checkDriverType ($ this ->targetStorage )) {
72+ $ source = $ this ->resourceFactory ->getFolderObjectFromCombinedIdentifier ($ input ->getArgument ('source ' ));
73+ $ sourceStorage = $ source ->getStorage ();
74+ $ sourceStorageDriver = $ this ->getStorageDriver ($ sourceStorage );
75+
76+ $ target = $ this ->resourceFactory ->getFolderObjectFromCombinedIdentifier ($ input ->getArgument ('target ' ));
77+ $ targetIndexer = GeneralUtility::makeInstance (Indexer::class, $ target ->getStorage ());
78+
79+ if (!$ sourceStorage ->hasHierarchicalIdentifiers ()) {
80+ $ this ->log ('Source storage must use hierarchical identifiers ' );
81+ return Command::INVALID ;
82+ }
83+ if (!$ this ->checkDriverType ($ target ->getStorage ())) {
7484 $ this ->log ('Look out! target storage is not of type "cloudinary" ' );
7585 return Command::INVALID ;
7686 }
7787
78- $ files = $ this ->getFiles ($ this ->sourceStorage , $ input );
79-
88+ $ files = $ this ->getFiles ($ source , $ input );
8089 if (count ($ files ) === 0 ) {
8190 $ this ->log ('No files found, no work for me! ' );
8291 return Command::SUCCESS ;
8392 }
8493
8594 $ this ->log ('I will process %s files to be moved from storage "%s" (%s) to "%s" (%s) ' , [
8695 count ($ files ),
87- $ this -> sourceStorage -> getUid (),
88- $ this -> sourceStorage ->getName (),
89- $ this -> targetStorage -> getUid (),
90- $ this -> targetStorage ->getName (),
96+ $ source -> getCombinedIdentifier (),
97+ $ sourceStorage ->getName (),
98+ $ target -> getCombinedIdentifier (),
99+ $ target -> getStorage () ->getName (),
91100 ]);
92101
93102 // A chance to the user to confirm the action
@@ -96,33 +105,36 @@ protected function execute(InputInterface $input, OutputInterface $output): int
96105
97106 if (!$ response ) {
98107 $ this ->log ('Script aborted ' );
99-
100- return Command::SUCCESS ;
108+ return Command::SUCCESS ;
101109 }
102110 }
103111
104- /** @var ResourceFactory $resourceFactory */
105- $ resourceFactory = GeneralUtility::makeInstance (ResourceFactory::class);
106-
107112 $ counter = 0 ;
108113 foreach ($ files as $ file ) {
109114 $ this ->log ();
110115 $ this ->log ('Starting migration with %s ' , [$ file ['identifier ' ]]);
111116
112- /** @var $fileObject */
113- $ fileObject = $ resourceFactory ->getFileObjectByStorageAndIdentifier ($ this ->sourceStorage ->getUid (), $ file ['identifier ' ]);
117+ /** @var File $fileObject */
118+ $ fileObject = $ this ->resourceFactory ->getFileObject ($ file ['uid ' ], $ file );
119+ $ sourceFileExists = $ fileObject ->exists ();
114120
115121 if ($ this ->isFileSkipped ($ fileObject )) {
116122 $ this ->log ('Skipping file ' . $ fileObject ->getIdentifier ());
117123 // $this->skippedFiles[] = $fileObject->getIdentifier();
118124 continue ;
119125 }
120126
121- if ($ this ->getFileMoveService ()->fileExists ($ fileObject , $ this ->targetStorage )) {
127+ if (! str_starts_with ($ fileObject ->getIdentifier (), $ source ->getIdentifier ())) {
128+ throw new \LogicException ('file is not in source folder ' , 1748004982814 );
129+ }
130+
131+ $ newIdentifier = str_replace ($ source ->getIdentifier (), $ target ->getIdentifier (), $ fileObject ->getIdentifier ());
132+
133+ if ($ this ->getFileMoveService ()->fileExists ($ target ->getStorage (), $ newIdentifier )) {
122134 $ this ->log ('File has already been uploaded, good for us %s ' , [$ fileObject ->getIdentifier ()]);
123135 } else {
124136 // Detect if the file is existing on storage "source" (1)
125- if (!$ fileObject -> exists () && !$ input ->getOption ('base-url ' )) {
137+ if (!$ sourceFileExists && !$ input ->getOption ('base-url ' )) {
126138 $ this ->log ('Missing file %s ' , [$ fileObject ->getIdentifier ()], self ::WARNING );
127139 // We could log the missing files
128140 $ this ->missingFiles [] = $ fileObject ->getIdentifier ();
@@ -134,7 +146,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
134146
135147 try {
136148 $ start = microtime (true );
137- $ this ->getFileMoveService ()->cloudinaryUploadFile ($ fileObject , $ this -> targetStorage , $ input ->getOption ('base-url ' ));
149+ $ this ->getFileMoveService ()->cloudinaryUploadFile ($ fileObject , $ target , $ newIdentifier , $ input ->getOption ('base-url ' ));
138150 $ timeElapsedSeconds = microtime (true ) - $ start ;
139151 $ this ->log ('File uploaded, Elapsed time %.3f ' , [$ timeElapsedSeconds ]);
140152 } catch (Exception $ e ) {
@@ -148,9 +160,25 @@ protected function execute(InputInterface $input, OutputInterface $output): int
148160 }
149161 }
150162
151- // changing file storage and hard delete the file from the current storage
163+ // Update sys_file entry
164+ // See \TYPO3\CMS\Core\Resource\ResourceStorage::moveFile for reference
152165 $ this ->log ('Changing storage for file %s ' , [$ fileObject ->getIdentifier ()]);
153- $ this ->getFileMoveService ()->changeStorage ($ fileObject , $ this ->targetStorage );
166+ $ oldIdentifier = $ fileObject ->getIdentifier ();
167+ $ oldFolder = $ fileObject ->getParentFolder ();
168+ $ fileObject ->updateProperties (['storage ' => $ target ->getStorage ()->getUid (), 'identifier ' => $ newIdentifier ]);
169+ $ newFolder = $ fileObject ->getParentFolder ();
170+ // clean up processed files
171+ $ this ->eventDispatcher ->dispatch (new AfterFileAddedEvent ($ fileObject , $ newFolder ));
172+ $ targetIndexer ->updateIndexEntry ($ fileObject );
173+
174+ // Delete the file from the source storage without deleting the sys_file record
175+ if ($ sourceFileExists ) {
176+ $ sourceStorageDriver ->deleteFile ($ oldIdentifier );
177+ $ sourceFileExists = false ;
178+ }
179+
180+ $ this ->eventDispatcher ->dispatch (new AfterFileMovedEvent ($ fileObject , $ newFolder , $ oldFolder ));
181+
154182 $ counter ++;
155183 }
156184 $ this ->log (LF );
@@ -208,4 +236,12 @@ protected function getFileMoveService(): FileMoveService
208236 {
209237 return GeneralUtility::makeInstance (FileMoveService::class);
210238 }
239+
240+ protected function getStorageDriver (ResourceStorage $ storage ): DriverInterface
241+ {
242+ $ reflection = new \ReflectionClass ($ storage );
243+ $ property = $ reflection ->getProperty ('driver ' );
244+ $ property ->setAccessible (true );
245+ return $ property ->getValue ($ storage );
246+ }
211247}
0 commit comments