Home Download Docs Code Community
     1	/*
     2	Copyright 2013 The Perkeep Authors
     3	
     4	Licensed under the Apache License, Version 2.0 (the "License");
     5	you may not use this file except in compliance with the License.
     6	You may obtain a copy of the License at
     7	
     8	     http://www.apache.org/licenses/LICENSE-2.0
     9	
    10	Unless required by applicable law or agreed to in writing, software
    11	distributed under the License is distributed on an "AS IS" BASIS,
    12	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13	See the License for the specific language governing permissions and
    14	limitations under the License.
    15	*/
    16	
    17	package camtypes
    18	
    19	import (
    20		"bytes"
    21		"fmt"
    22		"math"
    23		"mime"
    24		"path/filepath"
    25		"strings"
    26		"time"
    27	
    28		"perkeep.org/internal/magic"
    29		"perkeep.org/pkg/blob"
    30		"perkeep.org/pkg/schema"
    31	
    32		"go4.org/types"
    33	)
    34	
    35	type RecentPermanode struct {
    36		Permanode   blob.Ref
    37		Signer      blob.Ref // may be zero (!Valid())
    38		LastModTime time.Time
    39	}
    40	
    41	func (a RecentPermanode) Equal(b RecentPermanode) bool {
    42		return a.Permanode == b.Permanode &&
    43			a.Signer == b.Signer &&
    44			a.LastModTime.Equal(b.LastModTime)
    45	}
    46	
    47	type Claim struct {
    48		// TODO: document/decide how to represent "multi" claims here. One Claim each? Add Multi in here?
    49		// Move/merge this in with the schema package?
    50	
    51		BlobRef, Signer blob.Ref
    52	
    53		Date time.Time
    54		Type string // "set-attribute", "add-attribute", etc
    55	
    56		// If an attribute modification
    57		Attr, Value string
    58		Permanode   blob.Ref
    59	
    60		// If a DeleteClaim or a ShareClaim
    61		Target blob.Ref
    62	}
    63	
    64	func (c *Claim) String() string {
    65		return fmt.Sprintf(
    66			"camtypes.Claim{BlobRef: %s, Signer: %s, Permanode: %s, Date: %s, Type: %s, Attr: %s, Value: %s}",
    67			c.BlobRef, c.Signer, c.Permanode, c.Date, c.Type, c.Attr, c.Value)
    68	}
    69	
    70	type ClaimPtrsByDate []*Claim
    71	
    72	func (cl ClaimPtrsByDate) Len() int           { return len(cl) }
    73	func (cl ClaimPtrsByDate) Less(i, j int) bool { return cl[i].Date.Before(cl[j].Date) }
    74	func (cl ClaimPtrsByDate) Swap(i, j int)      { cl[i], cl[j] = cl[j], cl[i] }
    75	
    76	type ClaimsByDate []Claim
    77	
    78	func (cl ClaimsByDate) Len() int           { return len(cl) }
    79	func (cl ClaimsByDate) Less(i, j int) bool { return cl[i].Date.Before(cl[j].Date) }
    80	func (cl ClaimsByDate) Swap(i, j int)      { cl[i], cl[j] = cl[j], cl[i] }
    81	
    82	func (cl ClaimsByDate) String() string {
    83		var buf bytes.Buffer
    84		fmt.Fprintf(&buf, "[%d claims: ", len(cl))
    85		for _, r := range cl {
    86			buf.WriteString(r.String())
    87		}
    88		buf.WriteString("]")
    89		return buf.String()
    90	}
    91	
    92	// FileInfo describes a file or directory.
    93	type FileInfo struct {
    94		// FileName is the base name of the file or directory.
    95		FileName string `json:"fileName"`
    96	
    97		// Size is the size of file, or if a directory, the number of
    98		// its children.
    99		Size int64 `json:"size"`
   100	
   101		// MIMEType may be set for files, but never for directories.
   102		MIMEType string `json:"mimeType,omitempty"`
   103	
   104		// Time is the earliest of any modtime, creation time, or EXIF
   105		// original/modification times found. It may be omitted (zero)
   106		// if unknown.
   107		Time *types.Time3339 `json:"time,omitempty"`
   108	
   109		// ModTime is the latest of any modtime, creation time, or EXIF
   110		// original/modification times found. If ModTime doesn't differ
   111		// from Time, ModTime is omitted (zero).
   112		ModTime *types.Time3339 `json:"modTime,omitempty"`
   113	
   114		// WholeRef is the digest of the entire file contents.
   115		// This will be zero for non-regular files, and may also be zero
   116		// for files above a certain size threshold.
   117		WholeRef blob.Ref `json:"wholeRef,omitempty"`
   118	}
   119	
   120	func (fi *FileInfo) IsText() bool {
   121		if strings.HasPrefix(fi.MIMEType, "text/") {
   122			return true
   123		}
   124	
   125		return strings.HasPrefix(mime.TypeByExtension(filepath.Ext(fi.FileName)), "text/")
   126	}
   127	
   128	func (fi *FileInfo) IsImage() bool {
   129		if strings.HasPrefix(fi.MIMEType, "image/") {
   130			return true
   131		}
   132	
   133		return strings.HasPrefix(mime.TypeByExtension(filepath.Ext(fi.FileName)), "image/")
   134	}
   135	
   136	func (fi *FileInfo) IsVideo() bool {
   137		if strings.HasPrefix(fi.MIMEType, "video/") {
   138			return true
   139		}
   140	
   141		if magic.IsVideoFileName(fi.FileName) {
   142			return true
   143		}
   144	
   145		return strings.HasPrefix(mime.TypeByExtension(filepath.Ext(fi.FileName)), "video/")
   146	}
   147	
   148	// ImageInfo describes an image file.
   149	//
   150	// The Width and Height are uint16s to save memory in index/corpus.go, and that's
   151	// the max size of a JPEG anyway. If we want to deal with larger sizes, we can use
   152	// MaxUint16 as a sentinel to mean to look elsewhere. Or ditch this optimization.
   153	type ImageInfo struct {
   154		// Width is the visible width of the image (after any necessary EXIF rotation).
   155		Width uint16 `json:"width"`
   156		// Height is the visible height of the image (after any necessary EXIF rotation).
   157		Height uint16 `json:"height"`
   158	}
   159	
   160	type Path struct {
   161		Claim, Base, Target blob.Ref
   162		ClaimDate           time.Time
   163		Suffix              string // ??
   164	}
   165	
   166	func (p *Path) String() string {
   167		return fmt.Sprintf("Path{Claim: %v, %v; Base: %v + Suffix %q => Target %v}",
   168			p.Claim, p.ClaimDate, p.Base, p.Suffix, p.Target)
   169	}
   170	
   171	type PermanodeByAttrRequest struct {
   172		Signer blob.Ref
   173	
   174		// Attribute to search. currently supported: "tag", "title"
   175		// If FuzzyMatch is set, this can be blank to search all
   176		// attributes.
   177		Attribute string
   178	
   179		// The attribute value to find exactly (or roughly, if
   180		// FuzzyMatch is set)
   181		// If blank, the permanodes with Attribute as an attribute
   182		// (set to any value) are searched.
   183		Query string
   184	
   185		FuzzyMatch bool // by default, an exact match is required
   186		MaxResults int  // optional max results
   187	
   188		// At, if non-zero, specifies that the attribute must have been set at
   189		// the latest at At.
   190		At time.Time
   191	}
   192	
   193	type EdgesToOpts struct {
   194		Max int
   195		// TODO: filter by type?
   196	}
   197	
   198	type Edge struct {
   199		From      blob.Ref
   200		FromType  schema.CamliType // "permanode", "directory", etc
   201		FromTitle string           // name of source permanode or directory
   202		To        blob.Ref
   203		BlobRef   blob.Ref // the blob responsible for the edge relationship
   204	}
   205	
   206	func (e *Edge) String() string {
   207		return fmt.Sprintf("[edge from:%s to:%s type:%s title:%s]", e.From, e.To, e.FromType, e.FromTitle)
   208	}
   209	
   210	// BlobMeta is the metadata kept for each known blob in the in-memory
   211	// search index. It's kept as small as possible to save memory.
   212	type BlobMeta struct {
   213		Ref  blob.Ref
   214		Size uint32
   215	
   216		// CamliType is non-empty if this blob is a Perkeep JSON
   217		// schema blob. If so, this is its "camliType" attribute.
   218		CamliType schema.CamliType
   219	
   220		// TODO(bradfitz): change CamliTypethis *string to save 8 bytes
   221	}
   222	
   223	// SearchErrorResponse is the JSON error response for a search request.
   224	type SearchErrorResponse struct {
   225		Error     string `json:"error,omitempty"`     // The error message.
   226		ErrorType string `json:"errorType,omitempty"` // The type of the error.
   227	}
   228	
   229	// FileSearchResponse is the JSON response to a file search request.
   230	type FileSearchResponse struct {
   231		SearchErrorResponse
   232	
   233		// Files maps a requested wholeRef to the files found for it. Never nil.
   234		Files map[string][]blob.Ref `json:"files"`
   235	}
   236	
   237	// Location describes a file or permanode that has a location.
   238	type Location struct {
   239		// Latitude and Longitude represent the point location of this blob,
   240		// such as the place where a photo was taken.
   241		//
   242		// Negative values represent positions south of the equator or
   243		// west of the prime meridian:
   244		// Northern latitudes are positive, southern latitudes are negative.
   245		// Eastern longitudes are positive, western longitudes are negative.
   246		Latitude  float64 `json:"latitude"`
   247		Longitude float64 `json:"longitude"`
   248	
   249		// TODO(tajtiattila): decide how to represent blobs with
   250		// no single point location such as a track file once we index them,
   251		// perhaps with a N/S/E/W boundary. Note that a single point location
   252		// is still useful for these, to represent the starting point of a
   253		// track log or the main entrance of an area or building.
   254	}
   255	
   256	type Longitude float64
   257	
   258	// WrapTo180 returns l converted to the [-180,180] interval.
   259	func (l Longitude) WrapTo180() float64 {
   260		lf := float64(l)
   261		if lf >= -180 && lf <= 180 {
   262			return lf
   263		}
   264		if lf == 0 {
   265			return lf
   266		}
   267		if lf > 0 {
   268			return math.Mod(lf+180, 360) - 180
   269		}
   270		return math.Mod(lf-180, 360) + 180
   271	}
   272	
   273	// LocationBounds is a location area delimited by its fields. See Location for
   274	// the fields meanings and values.
   275	type LocationBounds struct {
   276		North float64 `json:"north"`
   277		South float64 `json:"south"`
   278		West  float64 `json:"west"`
   279		East  float64 `json:"east"`
   280	}
   281	
   282	// SpansDateLine reports whether b spans the antimeridian international date line.
   283	func (b LocationBounds) SpansDateLine() bool { return b.East < b.West }
   284	
   285	// Contains reports whether loc is in the bounds b.
   286	func (b LocationBounds) Contains(loc Location) bool {
   287		if b.SpansDateLine() {
   288			return loc.Longitude >= b.West || loc.Longitude <= b.East
   289		}
   290		return loc.Longitude >= b.West && loc.Longitude <= b.East
   291	}
   292	
   293	func (b LocationBounds) Width() float64 {
   294		if !b.SpansDateLine() {
   295			return b.East - b.West
   296		}
   297		return b.East - b.West + 360
   298	}
   299	
   300	// Expand returns a new LocationBounds nb. If either of loc coordinates is
   301	// outside of b, nb is the dimensions of b expanded as little as possible in
   302	// order to include loc. Otherwise, nb is just a copy of b.
   303	func (b LocationBounds) Expand(loc Location) LocationBounds {
   304		if b == (LocationBounds{}) {
   305			return LocationBounds{
   306				North: loc.Latitude,
   307				South: loc.Latitude,
   308				West:  loc.Longitude,
   309				East:  loc.Longitude,
   310			}
   311		}
   312		nb := LocationBounds{
   313			North: b.North,
   314			South: b.South,
   315			West:  b.West,
   316			East:  b.East,
   317		}
   318		if loc.Latitude > nb.North {
   319			nb.North = loc.Latitude
   320		} else if loc.Latitude < nb.South {
   321			nb.South = loc.Latitude
   322		}
   323		if nb.Contains(loc) {
   324			return nb
   325		}
   326		center := nb.center()
   327		dToCenter := center.Longitude - loc.Longitude
   328		if math.Abs(dToCenter) <= 180 {
   329			if dToCenter > 0 {
   330				// expand Westwards
   331				nb.West = loc.Longitude
   332			} else {
   333				// expand Eastwards
   334				nb.East = loc.Longitude
   335			}
   336			return nb
   337		}
   338		if dToCenter > 0 {
   339			// expand Eastwards
   340			nb.East = loc.Longitude
   341		} else {
   342			// expand Westwards
   343			nb.West = loc.Longitude
   344		}
   345		return nb
   346	}
   347	
   348	func (b *LocationBounds) center() Location {
   349		var lat, long float64
   350		lat = b.South + (b.North-b.South)/2.
   351		if b.West < b.East {
   352			long = b.West + (b.East-b.West)/2.
   353			return Location{
   354				Latitude:  lat,
   355				Longitude: long,
   356			}
   357		}
   358		// b is spanning over antimeridian
   359		awest := math.Abs(b.West)
   360		aeast := math.Abs(b.East)
   361		if awest > aeast {
   362			long = b.East - (awest-aeast)/2.
   363		} else {
   364			long = b.West + (aeast-awest)/2.
   365		}
   366		return Location{
   367			Latitude:  lat,
   368			Longitude: long,
   369		}
   370	}
Website layout inspired by memcached.
Content by the authors.