package job import ( "fmt" "os" "sort" "strings" "time" ) type StringSlice sort.StringSlice type Job struct { Id uint Archived bool Customer string Name string Taxa []string Tags sort.StringSlice Watching sort.StringSlice Notified map[string]time.Time } func NewJob(id uint, customer string, name string) *Job { job := &Job{ Id: id, Archived: false, Customer: customer, Name: name, Taxa: sort.StringSlice{}, Tags: sort.StringSlice{}, Watching: sort.StringSlice{}, Notified: map[string]time.Time{}, } return job } func union(slices ...sort.StringSlice) sort.StringSlice { set := map[string]bool{} for _, slice := range slices { for _, key := range slice { set[key] = true } } keys := make(sort.StringSlice, len(set)) i := 0 for key, _ := range set { keys[i] = key i++ } keys.Sort() return keys } func (job *Job) AddTaxa(taxa ...string) { job.Taxa = append(job.Taxa, taxa...) } func (job *Job) AddTags(tags ...string) { job.Tags = union(job.Tags, tags) } func (job *Job) Watch(who ...string) { job.Watching = union(job.Watching, who) } func (job *Job) Notify(who ...string) { for _, user := range who { job.Notified[user] = time.Now() } } func (job *Job) Shard() string { upper := job.Id / 1000 return fmt.Sprintf("%dxxx", upper) } func (job *Job) Path() string { return fmt.Sprintf("Jobs/%s/%d", job.Shard(), job.Id) } func (job *Job) TaxonomyDirectories() sort.StringSlice { n := len(job.Taxa) parts := make([][]string, (n*n+n)/2) // e.g. [x,y,z] -> [[x], [x, y], [x, y, z]] for i := range job.Taxa { parts[i] = job.Taxa[0 : i+1] } // e.g. [[x], [x, y], [x, y, z]] -> [[y], [y, z]] + [[z]] k := n for i := 1; i < n; i++ { for j := i; j < n; j++ { parts[k] = job.Taxa[i : j+1] k++ } } dirs := make([]string, k) upper := job.Id / 1000 for i, part := range parts { dirs[i] = fmt.Sprintf("Customers/%s/%s/• Jobs/%dxxx", job.Customer, strings.Join(part, "/"), upper) } return dirs } func (job *Job) CustomerDirectory() string { return fmt.Sprintf("Customers/%s/• Jobs/%s", job.Customer, job.Shard()) } func (job *Job) SymlinkDirectories() sort.StringSlice { customer := sort.StringSlice{job.CustomerDirectory()} taxonomy := job.TaxonomyDirectories() return union(customer, taxonomy) } func (job *Job) AllDirectories() sort.StringSlice { original := sort.StringSlice{job.Path()} symlinks := job.SymlinkDirectories() return union(original, symlinks) } func (job *Job) Description() string { return fmt.Sprintf("%d | %s | %s", job.Id, strings.Join(job.Taxa, " ▶ "), job.Name) } func (job *Job) Create(where string) error { for _, dir := range job.AllDirectories() { err := os.MkdirAll(where+"/"+dir, 0755) if err != nil { return err } } original := job.Path() description := job.Description() for _, dir := range job.SymlinkDirectories() { err := os.Remove(where + "/" + dir + "/." + description) if err != nil && !os.IsNotExist(err) { return err } err = os.Symlink(where+"/"+original, where+"/"+dir+"/."+description) if err != nil { return err } err = os.Rename(where+"/"+dir+"/."+description, where+"/"+dir+"/"+description) if err != nil { return err } } return nil }