diff --git a/agent/app/repo/app.go b/agent/app/repo/app.go index c7bbc7c87abf..cc487091b00f 100644 --- a/agent/app/repo/app.go +++ b/agent/app/repo/app.go @@ -20,6 +20,7 @@ type IAppRepo interface { WithResource(resource string) DBOption WithNotLocal() DBOption WithByLikeName(name string) DBOption + WithKeyIn(keys []string) DBOption WithArch(arch string) DBOption WithPanelVersion(panelVersion string) DBOption @@ -55,6 +56,12 @@ func (a AppRepo) WithKey(key string) DBOption { } } +func (a AppRepo) WithKeyIn(keys []string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("`key` in (?)", keys) + } +} + func (a AppRepo) WithType(typeStr string) DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("type = ?", typeStr) diff --git a/agent/app/service/app.go b/agent/app/service/app.go index 7ab5332c4f1a..d90766428ad2 100644 --- a/agent/app/service/app.go +++ b/agent/app/service/app.go @@ -12,6 +12,7 @@ import ( "reflect" "strconv" "strings" + "sync" "github.com/gin-gonic/gin" @@ -34,6 +35,11 @@ import ( "gopkg.in/yaml.v3" ) +var ( + appStoreSyncMu sync.Mutex + appStoreSyncing bool +) + type AppService struct { } @@ -840,6 +846,13 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) { res.CanUpdate = true return res, err } + if appicon.IsIconFile(app.Icon) { + fileName, _ := appicon.ParseIconField(app.Icon) + if fileName == "" || !appicon.IconFileExists(fileName) { + res.CanUpdate = true + return res, err + } + } } list, err := getAppList() @@ -897,33 +910,61 @@ var InitTypes = map[string]struct{}{ } func deleteCustomApp() { + installs, err := appInstallRepo.ListBy(context.Background()) + if err != nil { + global.LOG.Errorf("[AppStore] deleteCustomApp: failed to list installs, skipping: %v", err) + return + } var appIDS []uint - installs, _ := appInstallRepo.ListBy(context.Background()) for _, install := range installs { appIDS = append(appIDS, install.AppId) } var ops []repo.DBOption - ops = append(ops, repo.WithByIDNotIn(appIDS)) if len(appIDS) > 0 { ops = append(ops, repo.WithByIDNotIn(appIDS)) } - apps, _ := appRepo.GetBy(ops...) + apps, err := appRepo.GetBy(ops...) + if err != nil { + global.LOG.Errorf("[AppStore] deleteCustomApp: failed to get apps, skipping: %v", err) + return + } var deleteIDS []uint for _, app := range apps { if app.Resource == constant.AppResourceCustom { deleteIDS = append(deleteIDS, app.ID) } } - _ = appRepo.DeleteByIDs(context.Background(), deleteIDS) - _ = appDetailRepo.DeleteByAppIds(context.Background(), deleteIDS) + if len(deleteIDS) == 0 { + return + } + if err = appRepo.DeleteByIDs(context.Background(), deleteIDS); err != nil { + global.LOG.Errorf("[AppStore] deleteCustomApp: failed to delete apps: %v", err) + } + if err = appDetailRepo.DeleteByAppIds(context.Background(), deleteIDS); err != nil { + global.LOG.Errorf("[AppStore] deleteCustomApp: failed to delete app details: %v", err) + } } func (a AppService) SyncAppListFromRemote(taskID string) (err error) { if xpack.IsUseCustomApp() { return nil } + + appStoreSyncMu.Lock() + global.LOG.Info("[AppStore] sync app from remote task create start") + if appStoreSyncing { + appStoreSyncMu.Unlock() + global.LOG.Info("[AppStore] sync already in progress, skipping") + return nil + } + appStoreSyncing = true + appStoreSyncMu.Unlock() + syncTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore, taskID, 0) if err != nil { + appStoreSyncMu.Lock() + appStoreSyncing = false + appStoreSyncMu.Unlock() return err } @@ -933,13 +974,29 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) { syncTask.AddSubTask(i18n.GetMsgByKey("SyncAppDetail"), a.createSyncAppStoreMetaTask(&sharedCtx), nil) go func() { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("[AppStore] sync goroutine recovered from panic: %v", r) + if updateErr := NewISettingService().Update("AppStoreSyncStatus", constant.StatusError); updateErr != nil { + global.LOG.Warnf("[AppStore] failed to update sync status after panic: %v", updateErr) + } + } + appStoreSyncMu.Lock() + appStoreSyncing = false + appStoreSyncMu.Unlock() + }() if err := syncTask.Execute(); err != nil { - _ = NewISettingService().Update("AppStoreLastModified", "0") - _ = NewISettingService().Update("AppStoreSyncStatus", constant.StatusError) + if updateErr := NewISettingService().Update("AppStoreLastModified", "0"); updateErr != nil { + global.LOG.Warnf("[AppStore] failed to reset last modified: %v", updateErr) + } + if updateErr := NewISettingService().Update("AppStoreSyncStatus", constant.StatusError); updateErr != nil { + global.LOG.Warnf("[AppStore] failed to update sync status to error: %v", updateErr) + } return } }() + global.LOG.Info("[AppStore] sync app from remote task create ok") return nil } diff --git a/agent/app/service/app_sync_task.go b/agent/app/service/app_sync_task.go index 7c547e957429..5c83c65bec0d 100644 --- a/agent/app/service/app_sync_task.go +++ b/agent/app/service/app_sync_task.go @@ -68,7 +68,9 @@ func (a AppService) createSyncAppStoreTask(sharedCtx **appSyncContext) func(t *t } settingService := NewISettingService() - _ = settingService.Update("AppStoreSyncStatus", constant.StatusSyncing) + if err := settingService.Update("AppStoreSyncStatus", constant.StatusSyncing); err != nil { + global.LOG.Warnf("[AppStore] failed to update sync status to syncing: %v", err) + } setting, err := settingService.GetSettingInfo() if err != nil { @@ -111,8 +113,12 @@ func (a AppService) createSyncAppStoreTask(sharedCtx **appSyncContext) func(t *t return err } - _ = settingService.Update("AppStoreSyncStatus", constant.StatusSyncSuccess) - _ = settingService.Update("AppStoreLastModified", strconv.Itoa(list.LastModified)) + if err := settingService.Update("AppStoreSyncStatus", constant.StatusSyncSuccess); err != nil { + global.LOG.Warnf("[AppStore] failed to update sync status to success: %v", err) + } + if err := settingService.Update("AppStoreLastModified", strconv.Itoa(list.LastModified)); err != nil { + global.LOG.Warnf("[AppStore] failed to update last modified: %v", err) + } global.LOG.Infof("[AppStore] Appstore sync completed") *sharedCtx = ctx @@ -342,6 +348,31 @@ func (c *appSyncContext) classifyAndPersistAppsWithStats(addCount, updateCount, } } + if len(addAppArray) > 0 { + addKeys := make([]string, 0, len(addAppArray)) + for _, app := range addAppArray { + addKeys = append(addKeys, app.Key) + } + existingApps, _ := appRepo.GetBy(appRepo.WithKeyIn(addKeys)) + if len(existingApps) > 0 { + existingMap := make(map[string]model.App, len(existingApps)) + for _, e := range existingApps { + existingMap[e.Key] = e + } + filteredAdd := make([]model.App, 0, len(addAppArray)) + for _, app := range addAppArray { + if existing, ok := existingMap[app.Key]; ok { + app.ID = existing.ID + app.Details = existing.Details + updateAppArray = append(updateAppArray, app) + } else { + filteredAdd = append(filteredAdd, app) + } + } + addAppArray = filteredAdd + } + } + *addCount = len(addAppArray) *updateCount = len(updateAppArray) *deleteCount = len(deleteAppArray) @@ -394,13 +425,10 @@ func (c *appSyncContext) classifyAndPersistAppsWithStats(addCount, updateCount, for _, tag := range app.TagsKey { tagId, ok := tagMap[tag] if ok { - exist, _ := appTagRepo.GetFirst(ctx, appTagRepo.WithByTagID(tagId), appTagRepo.WithByAppID(app.ID)) - if exist == nil { - c.appTags = append(c.appTags, &model.AppTag{ - AppId: app.ID, - TagId: tagId, - }) - } + c.appTags = append(c.appTags, &model.AppTag{ + AppId: app.ID, + TagId: tagId, + }) } } @@ -449,8 +477,19 @@ func (c *appSyncContext) classifyAndPersistAppsWithStats(addCount, updateCount, } } - if len(c.oldAppIds) > 0 { - if err = appTagRepo.DeleteByAppIds(ctx, deleteIds); err != nil { + syncedAppIds := make([]uint, 0, len(addAppArray)+len(updateAppArray)+len(deleteIds)) + for _, app := range addAppArray { + if app.ID > 0 { + syncedAppIds = append(syncedAppIds, app.ID) + } + } + for _, app := range updateAppArray { + syncedAppIds = append(syncedAppIds, app.ID) + } + syncedAppIds = append(syncedAppIds, deleteIds...) + + if len(syncedAppIds) > 0 { + if err = appTagRepo.DeleteByAppIds(ctx, syncedAppIds); err != nil { return } } @@ -461,6 +500,8 @@ func (c *appSyncContext) classifyAndPersistAppsWithStats(addCount, updateCount, } } - tx.Commit() + if err = tx.Commit().Error; err != nil { + return + } return nil } diff --git a/agent/app/service/app_utils.go b/agent/app/service/app_utils.go index 03d76d3de603..d597738a85c3 100644 --- a/agent/app/service/app_utils.go +++ b/agent/app/service/app_utils.go @@ -2147,15 +2147,21 @@ func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool, sslRe return nil } -func SyncTags(remoteProperties dto.ExtraProperties) error { +func SyncTags(remoteProperties dto.ExtraProperties) (err error) { tx, ctx := getTxAndContext() - defer tx.Rollback() - localTags, _ := tagRepo.All() + defer func() { + if err != nil { + tx.Rollback() + } + }() + localTags, err := tagRepo.All() + if err != nil { + return err + } localTagsMap := make(map[string]*model.Tag) for i := range localTags { localTagsMap[localTags[i].Key] = &localTags[i] } - var err error remoteTagsMap := make(map[string]*dto.Tag) for i := range remoteProperties.Tags { remoteTagsMap[remoteProperties.Tags[i].Key] = &remoteProperties.Tags[i] @@ -2163,7 +2169,9 @@ func SyncTags(remoteProperties dto.ExtraProperties) error { for key, localTag := range localTagsMap { if _, exists := remoteTagsMap[key]; !exists { - _ = tagRepo.DeleteByID(ctx, localTag.ID) + if err = tagRepo.DeleteByID(ctx, localTag.ID); err != nil { + return err + } } } @@ -2193,7 +2201,9 @@ func SyncTags(remoteProperties dto.ExtraProperties) error { } } - tx.Commit() + if err = tx.Commit().Error; err != nil { + return err + } return nil } diff --git a/agent/init/business/business.go b/agent/init/business/business.go index b3b9e44bb073..2e9e8576bb85 100644 --- a/agent/init/business/business.go +++ b/agent/init/business/business.go @@ -23,7 +23,10 @@ func syncApp() { if global.CONF.Base.IsOffLine { return } - _ = service.NewISettingService().Update("AppStoreSyncStatus", constant.StatusSyncSuccess) + setting, err := service.NewISettingService().GetSettingInfo() + if err == nil && setting.AppStoreSyncStatus == constant.StatusSyncing { + _ = service.NewISettingService().Update("AppStoreSyncStatus", constant.StatusSyncSuccess) + } if err := service.NewIAppService().SyncAppListFromRemote(""); err != nil { global.LOG.Errorf("App Store synchronization failed") return