diff --git a/plugins/manager.go b/plugins/manager.go index 55d699113..8ef8875cb 100644 --- a/plugins/manager.go +++ b/plugins/manager.go @@ -398,6 +398,22 @@ type PluginMetadata struct { SHA256 string } +// computeFileSHA256 computes the SHA-256 hash of a file without loading it into memory. +// This is used for quick change detection before full plugin compilation. +func computeFileSHA256(path string) (string, error) { + f, err := os.Open(path) + if err != nil { + return "", err + } + defer f.Close() + + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)), nil +} + // compiledPluginInfo holds the intermediate compilation result used by both // ExtractManifest and loadPluginWithConfig. type compiledPluginInfo struct { @@ -549,11 +565,35 @@ func (m *Manager) SyncPlugins(ctx context.Context, folder string) error { // Process files on disk for name, path := range filesOnDisk { + dbPlugin, exists := pluginsInDB[name] + + // Compute SHA256 first (lightweight operation) to check if plugin changed + sha256Hash, err := computeFileSHA256(path) + if err != nil { + log.Error(ctx, "Failed to compute SHA256 for plugin", "plugin", name, "path", path, err) + continue + } + + // If plugin exists in DB with same hash, skip full manifest extraction + if exists && dbPlugin.SHA256 == sha256Hash { + // Plugin unchanged - just update path in case folder moved + if dbPlugin.Path != path { + dbPlugin.Path = path + dbPlugin.UpdatedAt = now + if err := repo.Put(dbPlugin); err != nil { + log.Error(ctx, "Failed to update plugin path in DB", "plugin", name, err) + } + } + delete(pluginsInDB, name) + continue + } + + // Plugin is new or changed - need full manifest extraction metadata, err := m.ExtractManifest(path) if err != nil { log.Error(ctx, "Failed to extract manifest from plugin", "plugin", name, "path", path, err) // Store error in DB if plugin exists - if dbPlugin, exists := pluginsInDB[name]; exists { + if exists { dbPlugin.LastError = err.Error() dbPlugin.UpdatedAt = now if dbPlugin.Enabled { @@ -567,29 +607,20 @@ func (m *Manager) SyncPlugins(ctx context.Context, folder string) error { log.Error(ctx, "Failed to update plugin in DB", "plugin", name, err) } } + delete(pluginsInDB, name) continue } - dbPlugin, exists := pluginsInDB[name] if !exists { // New plugin - add to DB as disabled if err := m.addPluginToDB(ctx, repo, name, path, metadata); err != nil { log.Error(ctx, "Failed to add plugin to DB", "plugin", name, err) } - } else if dbPlugin.SHA256 != metadata.SHA256 { + } else { // Plugin changed - update DB if err := m.updatePluginInDB(ctx, repo, dbPlugin, path, metadata); err != nil { log.Error(ctx, "Failed to update plugin in DB", "plugin", name, err) } - } else { - // Plugin unchanged - update path in case folder moved - if dbPlugin.Path != path { - dbPlugin.Path = path - dbPlugin.UpdatedAt = now - if err := repo.Put(dbPlugin); err != nil { - log.Error(ctx, "Failed to update plugin path in DB", "plugin", name, err) - } - } } // Mark as processed delete(pluginsInDB, name) diff --git a/plugins/watcher.go b/plugins/watcher.go index 6754efa42..c8f2fa9dd 100644 --- a/plugins/watcher.go +++ b/plugins/watcher.go @@ -164,37 +164,47 @@ func (m *Manager) processPluginEvent(pluginName string, eventType notify.Event) } case actionUpdate: - // File changed - extract manifest, update DB, disable if enabled - metadata, err := m.ExtractManifest(wasmPath) + // File changed - check SHA256 first, then extract manifest if needed + sha256Hash, err := computeFileSHA256(wasmPath) if err != nil { - log.Error(m.ctx, "Failed to extract manifest from changed plugin", "plugin", pluginName, err) - // Try to update error in DB if plugin exists - if dbPlugin, getErr := repo.Get(pluginName); getErr == nil { - dbPlugin.LastError = err.Error() - dbPlugin.UpdatedAt = time.Now() - if dbPlugin.Enabled { - _ = m.UnloadPlugin(pluginName) - dbPlugin.Enabled = false - } - _ = repo.Put(dbPlugin) - } + log.Error(m.ctx, "Failed to compute SHA256 for changed plugin", "plugin", pluginName, err) return } dbPlugin, err := repo.Get(pluginName) if err != nil { - // Plugin not in DB yet, add it + // Plugin not in DB yet, need full manifest extraction to add it + metadata, extractErr := m.ExtractManifest(wasmPath) + if extractErr != nil { + log.Error(m.ctx, "Failed to extract manifest from new plugin", "plugin", pluginName, extractErr) + return + } if addErr := m.addPluginToDB(m.ctx, repo, pluginName, wasmPath, metadata); addErr != nil { log.Error(m.ctx, "Failed to add plugin to DB", "plugin", pluginName, addErr) } return } - // Check if actually changed - if dbPlugin.SHA256 == metadata.SHA256 { + // Check if actually changed using lightweight SHA256 comparison + if dbPlugin.SHA256 == sha256Hash { return // No actual change } + // Plugin changed - now extract full manifest + metadata, err := m.ExtractManifest(wasmPath) + if err != nil { + log.Error(m.ctx, "Failed to extract manifest from changed plugin", "plugin", pluginName, err) + // Update error in DB + dbPlugin.LastError = err.Error() + dbPlugin.UpdatedAt = time.Now() + if dbPlugin.Enabled { + _ = m.UnloadPlugin(pluginName) + dbPlugin.Enabled = false + } + _ = repo.Put(dbPlugin) + return + } + if err := m.updatePluginInDB(m.ctx, repo, dbPlugin, wasmPath, metadata); err != nil { log.Error(m.ctx, "Failed to update plugin in DB", "plugin", pluginName, err) }