@@ -14,20 +14,19 @@ package main
1414import (
1515 "codewind/models"
1616 "codewind/utils"
17+ "strconv"
1718 "strings"
1819 "time"
1920)
2021
21- /**
22- * ProjectList is the API entrypoint for other code in this application to perform operations against monitored projects:
23- * - Update project list from a GET response
24- * - Update project list from a WebSocket response
25- * - Process a file update and pass it to batch utility
26- *
27- * Behind the scenes, the ProjectList API calls are translated into channel messages and placed on the projectOperationChannel.
28- * This allows us to provide thread safety to the internal project list data, as that data will only ever be accessed
29- * by a single goroutine.
30- */
22+ // ProjectList is the API entrypoint for other code in this application to perform operations against monitored projects:
23+ // - Update project list from a GET response
24+ // - Update project list from a WebSocket response
25+ // - Process a file update and pass it to batch utility
26+ //
27+ // Behind the scenes, the ProjectList API calls are translated into channel messages and placed on the projectOperationChannel.
28+ // This allows us to provide thread safety to the internal project list data, as that data will only ever be accessed
29+ // by a single goroutine.
3130type ProjectList struct {
3231 projectOperationChannel chan * projectListChannelMessage
3332 pathToInstaller string // nullable
@@ -38,6 +37,7 @@ type receiveNewWatchEntriesMessage struct {
3837 project * models.ProjectToWatch
3938}
4039
40+ // NewProjectList ...
4141func NewProjectList (postOutputQueue * HttpPostOutputQueue , pathToInstallerParam string ) * ProjectList {
4242
4343 result := & ProjectList {}
@@ -159,7 +159,7 @@ func (projectList *ProjectList) channelListener(postOutputQueue *HttpPostOutputQ
159159 }
160160}
161161
162- /** Generate an overview of the state of the project list, including the projects being watched . */
162+ /** Inform the CLI of a file change on the specified project . */
163163func (projectList * ProjectList ) handleCliFileChangeUpdate (projectID string , projectsMap map [string ]* projectObject ) {
164164
165165 value , exists := projectsMap [projectID ]
@@ -175,7 +175,9 @@ func (projectList *ProjectList) handleCliFileChangeUpdate(projectID string, proj
175175 return
176176 }
177177
178- value .cliState .OnFileChangeEvent ()
178+ if value .cliState != nil {
179+ value .cliState .OnFileChangeEvent (value .project .ProjectCreationTime )
180+ }
179181
180182}
181183
@@ -311,18 +313,92 @@ func (projectList *ProjectList) handleUpdateProjectListFromWebSocket(webSocketUp
311313
312314}
313315
314- /**
315- * Synchronize the project in our projectsMap (if it exists), with the new 'projectToProcess' from the server.
316- * If it doesn't exist, create it.*/
316+ // Synchronize the project in our projectsMap (if it exists), with the new 'projectToProcess' from the server.
317+ // If it doesn't exist, create it.
317318func (projectList * ProjectList ) processProject (projectToProcess models.ProjectToWatch , projectsMap map [string ]* projectObject , postOutputQueue * HttpPostOutputQueue , watchService * WatchService ) {
318319
319320 currProjWatchState , exists := projectsMap [projectToProcess .ProjectID ]
320321 if exists {
321322 // If we have previously monitored this project...
322323
323- if currProjWatchState .project .PathToMonitor == projectToProcess .PathToMonitor {
324+ oldProjectToWatch := currProjWatchState .project
325+
326+ // This method may receive ProjectToWatch objects with either null or non-null
327+ // values for the `projectCreationTimeInAbsoluteMsecs` field. However, under no
328+ // circumstances should we ever replace a non-null value for this field with a
329+ // null field.
330+ //
331+ // For this reason, we carefully compare these values in this if block and
332+ // update accordingly.
333+ {
334+ pctUpdated := false
335+
336+ pctOldProjectToWatch := oldProjectToWatch .ProjectCreationTime
337+ pctNewProjectToWatch := projectToProcess .ProjectCreationTime
338+
339+ newPct := int64 (0 )
340+
341+ // If both the old and new values are not null, but the value has changed, then
342+ // use the new value.
343+ if pctNewProjectToWatch != 0 && pctOldProjectToWatch != 0 && pctNewProjectToWatch != pctOldProjectToWatch {
344+
345+ newPct = pctNewProjectToWatch
346+
347+ utils .LogInfo ("The project creation time has changed, when both values were non-null. Old: " + timestampToString (pctOldProjectToWatch ) + " New: " + timestampToString (pctNewProjectToWatch ) + " for project " + projectToProcess .ProjectID )
348+
349+ pctUpdated = true
350+ }
351+
352+ // If old is not-null, and new is null, then DON'T overwrite the old one with
353+ // the new one.
354+ if pctOldProjectToWatch != 0 && pctNewProjectToWatch == 0 {
355+
356+ newPct = pctOldProjectToWatch
357+
358+ utils .LogInfo (
359+ "Internal project creation state was preserved, despite receiving a project update w/o this value. Current: " + timestampToString (pctOldProjectToWatch ) + " Received: " + timestampToString (pctNewProjectToWatch ) + " for project " + projectToProcess .ProjectID )
360+
361+ newPtw := * (projectToProcess .Clone ())
362+ newPtw .ProjectCreationTime = newPct
363+
364+ if newPtw .ProjectCreationTime != pctOldProjectToWatch {
365+ utils .LogSevere ("Updated PTW field did not have correct projectCreationTime, for project " + projectToProcess .ProjectID )
366+ }
367+
368+ // Update the ptw, in case it is used by the following if block, but DONT call
369+ // po.updatePTW(...) with it.
370+ projectToProcess = newPtw
371+ pctUpdated = false // this is false so that updatePTW(...) is not called.
372+ }
373+
374+ // If the old is null, and the new is not null, then overwrite the old with the
375+ // new.
376+ if pctOldProjectToWatch == 0 && pctNewProjectToWatch != 0 {
377+
378+ newPct = pctNewProjectToWatch
379+
380+ utils .LogInfo ("The project creation time has changed. Old: " + timestampToString (pctOldProjectToWatch ) + " New: " + timestampToString (pctNewProjectToWatch ) + ", for project " + projectToProcess .ProjectID )
381+
382+ pctUpdated = true
383+
384+ }
385+
386+ if pctUpdated {
324387
325- oldProjectToWatch := currProjWatchState .project
388+ newPtw := * (projectToProcess .Clone ())
389+ newPtw .ProjectCreationTime = newPct
390+
391+ // Update the object itself, in case the if-branch below this one is executed.
392+ projectToProcess = newPtw
393+
394+ // This logic may cause the PO to be updated twice (once here, and once below,
395+ // but this is fine)
396+ currProjWatchState .project = & projectToProcess
397+ }
398+
399+ }
400+
401+ if currProjWatchState .project .PathToMonitor == projectToProcess .PathToMonitor {
326402
327403 fileToMonitor , err := utils .ConvertAbsoluteUnixStyleNormalizedPathToLocalFile (projectToProcess .PathToMonitor )
328404 if err != nil {
@@ -383,6 +459,10 @@ func (projectList *ProjectList) processProject(projectToProcess models.ProjectTo
383459
384460}
385461
462+ func timestampToString (ts int64 ) string {
463+ return strconv .FormatInt (ts , 10 )
464+ }
465+
386466/** This function is called with a new file change entry, which is filtered (if necessary) then patched to the project's batch utility object. */
387467func handleReceiveNewWatchEventEntries (projectMatch * models.ProjectToWatch , entry * models.WatchEventEntry , projectsMap map [string ]* projectObject ) {
388468
@@ -479,7 +559,7 @@ func (projectList *ProjectList) newProjectObject(project models.ProjectToWatch,
479559
480560 return & projectObject {
481561 & project ,
482- NewFileChangeEventBatchUtil (project .ProjectID , postOutputQueue , cliState ),
562+ NewFileChangeEventBatchUtil (project .ProjectID , postOutputQueue , projectList ),
483563 cliState , // May be null
484564 }, nil
485565}
0 commit comments