@@ -34,27 +34,20 @@ public function __construct(
3434 *
3535 * @param resource $source
3636 *
37- * @return array<string,array<string,string|array<string>> >
37+ * @return Generator<int, ImportEvent >
3838 *
3939 * @throws \InvalidArgumentException
4040 */
41- public function import ($ source , CalendarImpl $ calendar , CalendarImportOptions $ options ): array {
41+ public function import ($ source , CalendarImpl $ calendar , CalendarImportOptions $ options ): Generator {
4242 if (!is_resource ($ source )) {
4343 throw new InvalidArgumentException ('Invalid import source must be a file resource ' );
4444 }
45- switch ($ options ->getFormat ()) {
46- case 'ical ' :
47- return $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importText (...));
48- break ;
49- case 'jcal ' :
50- return $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importJson (...));
51- break ;
52- case 'xcal ' :
53- return $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importXml (...));
54- break ;
55- default :
56- throw new InvalidArgumentException ('Invalid import format ' );
57- }
45+ return match ($ options ->getFormat ()) {
46+ 'ical ' => $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importText (...)),
47+ 'jcal ' => $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importJson (...)),
48+ 'xcal ' => $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importXml (...)),
49+ default => throw new InvalidArgumentException ('Invalid import format ' ),
50+ };
5851 }
5952
6053 /**
@@ -64,7 +57,7 @@ public function import($source, CalendarImpl $calendar, CalendarImportOptions $o
6457 *
6558 * @return Generator<\Sabre\VObject\Component\VCalendar>
6659 */
67- public function importText ($ source ): Generator {
60+ public function importText ($ source, CalendarImportOptions | null $ options = null ): Generator {
6861 if (!is_resource ($ source )) {
6962 throw new InvalidArgumentException ('Invalid import source must be a file resource ' );
7063 }
@@ -86,6 +79,14 @@ public function importText($source): Generator {
8679 $ vObject = Reader::read ($ sObjectPrefix . $ sObjectContents . $ sObjectSuffix );
8780 $ timezones [$ tid ] = clone $ vObject ->VTIMEZONE ;
8881 }
82+ // object counts before streaming if requested
83+ if ($ options ?->getCounts()) {
84+ yield 'counts ' => [
85+ 'VEVENT ' => count ($ structure ['VEVENT ' ]),
86+ 'VTODO ' => count ($ structure ['VTODO ' ]),
87+ 'VJOURNAL ' => count ($ structure ['VJOURNAL ' ]),
88+ ];
89+ }
8990 // calendar components
9091 // for each component type, construct a full calendar object with all components
9192 // that match the same UID and appropriate time zones that are used in the components
@@ -117,7 +118,7 @@ public function importText($source): Generator {
117118 *
118119 * @return Generator<\Sabre\VObject\Component\VCalendar>
119120 */
120- public function importXml ($ source ): Generator {
121+ public function importXml ($ source, CalendarImportOptions | null $ options = null ): Generator {
121122 if (!is_resource ($ source )) {
122123 throw new InvalidArgumentException ('Invalid import source must be a file resource ' );
123124 }
@@ -133,6 +134,14 @@ public function importXml($source): Generator {
133134 $ vObject = Reader::readXml ($ sObjectPrefix . $ sObjectContents . $ sObjectSuffix );
134135 $ timezones [$ tid ] = clone $ vObject ->VTIMEZONE ;
135136 }
137+ // object counts before streaming if requested
138+ if ($ options ?->getCounts()) {
139+ yield 'counts ' => [
140+ 'VEVENT ' => count ($ structure ['VEVENT ' ]),
141+ 'VTODO ' => count ($ structure ['VTODO ' ]),
142+ 'VJOURNAL ' => count ($ structure ['VJOURNAL ' ]),
143+ ];
144+ }
136145 // calendar components
137146 // for each component type, construct a full calendar object with all components
138147 // that match the same UID and appropriate time zones that are used in the components
@@ -164,7 +173,7 @@ public function importXml($source): Generator {
164173 *
165174 * @return Generator<\Sabre\VObject\Component\VCalendar>
166175 */
167- public function importJson ($ source ): Generator {
176+ public function importJson ($ source, CalendarImportOptions | null $ options = null ): Generator {
168177 if (!is_resource ($ source )) {
169178 throw new InvalidArgumentException ('Invalid import source must be a file resource ' );
170179 }
@@ -179,7 +188,18 @@ public function importJson($source): Generator {
179188 }
180189 }
181190 // calendar components
182- foreach ($ importer ->getBaseComponents () as $ base ) {
191+ $ baseComponents = $ importer ->getBaseComponents ();
192+ // object counts before streaming if requested
193+ if ($ options ?->getCounts()) {
194+ $ counts = ['VEVENT ' => 0 , 'VTODO ' => 0 , 'VJOURNAL ' => 0 ];
195+ foreach ($ baseComponents as $ component ) {
196+ if (isset ($ counts [$ component ->name ])) {
197+ $ counts [$ component ->name ]++;
198+ }
199+ }
200+ yield 'counts ' => $ counts ;
201+ }
202+ foreach ($ baseComponents as $ base ) {
183203 $ vObject = new VCalendar ;
184204 $ vObject ->VERSION = clone $ importer ->VERSION ;
185205 $ vObject ->PRODID = clone $ importer ->PRODID ;
@@ -226,22 +246,34 @@ private function findTimeZones(VCalendar $vObject): array {
226246 * @param CalendarImportOptions $options
227247 * @param callable $generator<CalendarImportOptions>: Generator<\Sabre\VObject\Component\VCalendar>
228248 *
229- * @return array<string,array<string,string|array<string>> >
249+ * @return Generator<int, ImportEvent >
230250 */
231- public function importProcess ($ source , CalendarImpl $ calendar , CalendarImportOptions $ options , callable $ generator ): array {
251+ public function importProcess ($ source , CalendarImpl $ calendar , CalendarImportOptions $ options , callable $ generator ): Generator {
232252 $ calendarId = $ calendar ->getKey ();
233253 $ calendarUri = $ calendar ->getUri ();
234254 $ principalUri = $ calendar ->getPrincipalUri ();
235- $ outcome = [];
236- foreach ($ generator ($ source ) as $ vObject ) {
255+ foreach ($ generator ($ source , $ options ) as $ key => $ value ) {
256+ if ($ key === 'counts ' ) {
257+ yield new ImportCountEvent (
258+ vevent: $ value ['VEVENT ' ] ?? 0 ,
259+ vtodo: $ value ['VTODO ' ] ?? 0 ,
260+ vjournal: $ value ['VJOURNAL ' ] ?? 0 ,
261+ );
262+ continue ;
263+ }
264+ $ vObject = $ value ;
237265 $ components = $ vObject ->getBaseComponents ();
238266 // determine if the object has no base component types
239267 if (count ($ components ) === 0 ) {
240268 $ errorMessage = 'One or more objects discovered with no base component types ' ;
241269 if ($ options ->getErrors () === $ options ::ERROR_FAIL ) {
242270 throw new InvalidArgumentException ('Error importing calendar data: ' . $ errorMessage );
243271 }
244- $ outcome ['nbct ' ] = ['outcome ' => 'error ' , 'errors ' => [$ errorMessage ]];
272+ yield new ImportObjectEvent (
273+ disposition: ImportDisposition::Error,
274+ identifier: null ,
275+ errors: [$ errorMessage ]
276+ );
245277 continue ;
246278 }
247279 // determine if the object has more than one base component type
@@ -255,7 +287,11 @@ public function importProcess($source, CalendarImpl $calendar, CalendarImportOpt
255287 if ($ options ->getErrors () === $ options ::ERROR_FAIL ) {
256288 throw new InvalidArgumentException ('Error importing calendar data: ' . $ errorMessage );
257289 }
258- $ outcome ['mbct ' ] = ['outcome ' => 'error ' , 'errors ' => [$ errorMessage ]];
290+ yield new ImportObjectEvent (
291+ disposition: ImportDisposition::Error,
292+ identifier: null ,
293+ errors: [$ errorMessage ]
294+ );
259295 continue 2 ;
260296 }
261297 }
@@ -266,15 +302,23 @@ public function importProcess($source, CalendarImpl $calendar, CalendarImportOpt
266302 if ($ options ->getErrors () === $ options ::ERROR_FAIL ) {
267303 throw new InvalidArgumentException ('Error importing calendar data: ' . $ errorMessage );
268304 }
269- $ outcome ['noid ' ] = ['outcome ' => 'error ' , 'errors ' => [$ errorMessage ]];
305+ yield new ImportObjectEvent (
306+ disposition: ImportDisposition::Error,
307+ identifier: null ,
308+ errors: [$ errorMessage ]
309+ );
270310 continue ;
271311 }
272312 $ uid = $ components [0 ]->UID ->getValue ();
273313 // validate object
274314 if ($ options ->getValidate () !== $ options ::VALIDATE_NONE ) {
275315 $ issues = $ this ->componentValidate ($ vObject , true , 3 );
276316 if ($ options ->getValidate () === $ options ::VALIDATE_SKIP && $ issues !== []) {
277- $ outcome [$ uid ] = ['outcome ' => 'error ' , 'errors ' => $ issues ];
317+ yield new ImportObjectEvent (
318+ disposition: ImportDisposition::Error,
319+ identifier: $ uid ,
320+ errors: $ issues
321+ );
278322 continue ;
279323 } elseif ($ options ->getValidate () === $ options ::VALIDATE_FAIL && $ issues !== []) {
280324 throw new InvalidArgumentException ('Error importing calendar data: UID < ' . $ uid . '> - ' . $ issues [0 ]);
@@ -291,7 +335,10 @@ public function importProcess($source, CalendarImpl $calendar, CalendarImportOpt
291335 $ objectId ,
292336 $ objectData
293337 );
294- $ outcome [$ uid ] = ['outcome ' => 'created ' ];
338+ yield new ImportObjectEvent (
339+ disposition: ImportDisposition::Created,
340+ identifier: $ uid ,
341+ );
295342 } else {
296343 [$ cid , $ oid ] = explode ('/ ' , $ objectId );
297344 if ($ options ->getSupersede ()) {
@@ -300,21 +347,29 @@ public function importProcess($source, CalendarImpl $calendar, CalendarImportOpt
300347 $ oid ,
301348 $ objectData
302349 );
303- $ outcome [$ uid ] = ['outcome ' => 'updated ' ];
350+ yield new ImportObjectEvent (
351+ disposition: ImportDisposition::Updated,
352+ identifier: $ uid ,
353+ );
304354 } else {
305- $ outcome [$ uid ] = ['outcome ' => 'exists ' ];
355+ yield new ImportObjectEvent (
356+ disposition: ImportDisposition::Exists,
357+ identifier: $ uid ,
358+ );
306359 }
307360 }
308361 } catch (Exception $ e ) {
309362 $ errorMessage = $ e ->getMessage ();
310363 if ($ options ->getErrors () === $ options ::ERROR_FAIL ) {
311364 throw new Exception ('Error importing calendar data: UID < ' . $ uid . '> - ' . $ errorMessage , 0 , $ e );
312365 }
313- $ outcome [$ uid ] = ['outcome ' => 'error ' , 'errors ' => [$ errorMessage ]];
366+ yield new ImportObjectEvent (
367+ disposition: ImportDisposition::Error,
368+ identifier: $ uid ,
369+ errors: [$ errorMessage ]
370+ );
314371 }
315372 }
316-
317- return $ outcome ;
318373 }
319374
320375 /**
0 commit comments