1
0
Fork 0
timelinize/tlzapp/state.go
Matt Holt 746e5d6b5c
Refactored import flow, new import UI, thumbnails stored in timeline, etc. (close #3) (#43)
* Schema revisions for new import flow and thumbnails

* WIP settings

* WIP quick schema fix

* gallery: Image search using ML embeddings

Still very rough around the edges, but basically works.

'uv' gets auto-installed, but currently requires restarting Timelinize before it can be used.

Lots of tunings and optimizations are needed. There is much room for improvement.

Still migrating from imports -> jobs, so that part of the code and schema is still a mess.

* Implement search for similar items

* Finish import/planning rewrite; it compiles and tests pass

* Fix some bugs, probably introduce other bugs

* WIP new import planning page

* Fix Google Photos and Twitter recognition

* Finish most of import page UI; start button still WIP

* WIP: Start Import button

* Fixes to jobs, thumbnail job, import job, etc.

* Implement proper checkpointing support; jobs fixes
2024-12-06 11:03:29 -07:00

102 lines
2.9 KiB
Go

/*
Timelinize
Copyright (c) 2013 Matthew Holt
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tlzapp
import (
"fmt"
"net/http"
"sync"
"github.com/google/uuid"
"github.com/timelinize/timelinize/timeline"
"go.uber.org/zap"
)
func shutdownTimelines() {
// TODO: we need cleaner shutdowns, that wait until processing is complete after jobs are cancelled -- which allows updating import status properly (when possible)
// TODO: this is from the old jobs system
// activeJobsMu.Lock()
// for _, job := range activeJobs {
// // can't call job.cleanUp() because we have the lock on activeJobs right now
// job.cancel()
// delete(activeJobs, job.ID)
// logger := timeline.Log.With(zap.Time("started", job.Started))
// if job.Type == "import" {
// logger = timeline.Log.With(
// zap.String("repo", job.ImportParameters.Repo),
// zap.Strings("filenames", job.ImportParameters.Filenames),
// zap.Int64("account_id", job.ImportParameters.AccountID),
// )
// }
// logger.Warn("canceled active job")
// }
// activeJobsMu.Unlock()
openTimelinesMu.Lock()
for key, tl := range openTimelines {
err := tl.Close()
if err != nil {
timeline.Log.Error("closing timeline",
zap.String("repo_path", tl.Dir()),
zap.String("instance_id", key),
zap.Error(err))
}
delete(openTimelines, key)
}
openTimelinesMu.Unlock()
}
// getOpenTimeline gets an open timeline designated by instance ID.
// If the timeline is not open, a structured error is returned.
func getOpenTimeline(repoID string) (openedTimeline, error) {
openTimelinesMu.RLock()
if repoID == "" {
// TODO: empty repo ID should probably mean ALL open repos;
// but if only 1 is open, at least return that
if len(openTimelines) == 1 {
for _, tl := range openTimelines {
openTimelinesMu.RUnlock()
return tl, nil
}
}
}
otl, ok := openTimelines[repoID]
openTimelinesMu.RUnlock()
if !ok {
return openedTimeline{}, fmt.Errorf("repository '%s' is not open", repoID)
}
return otl, nil
}
type openedTimeline struct {
RepoDir string `json:"repo_dir"` // absolute path
InstanceID uuid.UUID `json:"instance_id"`
*timeline.Timeline `json:"-"`
fileServer http.Handler
}
var (
openTimelines = make(map[string]openedTimeline) // keyed by serialization of instance UUID
openTimelinesMu sync.RWMutex
)