예제 #1
0
파일: list.go 프로젝트: cfstras/cfmedias
func (db *DB) list(ctx core.CommandContext) core.Result {
	//TODO
	args := ctx.Args
	var err error
	text, err := util.GetArg(args, "text", false, err)
	query, err := util.GetArg(args, "query", false, err)
	tags, err := util.GetArg(args, "tags", false, err)
	if text != nil {
		return core.ResultByError(core.ErrorNotImplemented)
	}
	if query != nil {
		res, err := db.ListQuery(*query)
		if err != nil {
			return core.ResultByError(errrs.New(err.Error()))
		}
		return core.Result{core.StatusOK, ItemToInterfaceSlice(res), err, false}
	}
	if tags != nil {
		return core.ResultByError(core.ErrorNotImplemented)
	}

	res, err := db.ListAll()
	if err != nil {
		return core.ResultByError(errrs.New(err.Error()))
	}
	return core.Result{core.StatusOK, ItemToInterfaceSlice(res), err, false}
}
예제 #2
0
파일: as.go 프로젝트: cfstras/cfmedias
func (a *AS) LoginAS(ctx core.CommandContext) core.Result {
	var err error
	args := ctx.Args
	handshakeS, err := util.GetArg(args, "hs", true, err)
	handshake, err := util.CastBool(handshakeS, err)
	pVer, err := util.GetArg(args, "p", true, err)
	_, err = util.GetArg(args, "c", true, err)
	_, err = util.GetArg(args, "v", true, err)
	userS, err := util.GetArg(args, "u", true, err)
	timestampS, err := util.GetArg(args, "t", true, err)
	timestamp, err := util.CastInt64(timestampS, err)
	authMsg, err := util.GetArg(args, "a", true, err)

	if err != nil {
		return errorAS(StatusFailed, err)
	}
	if !*handshake {
		return errorAS(StatusFailed, errrs.New("this URL is only for handshake"+
			" requests, hs must be true"))
	}
	if *pVer != "1.2.1" {
		return errorAS(StatusFailed, errrs.New("Protocol version must be 1.2.1"))
	}

	//TODO maybe check audioscrobbler client id and version

	// check timestamp
	timestampReal := time.Now().Unix()
	if util.Abs(timestampReal-*timestamp) > 120 {
		return errorAS(StatusBadTime, nil)
	}

	// get user
	user, err := a.db.GetUserByName(*userS)
	if user == nil {
		logger.Log.Println("incorrect auth from unknown user ", *userS)
		return errorAS(StatusBadAuth, nil)
	}
	// check auth
	// md5(md5(auth_token) + timestamp)
	md5Token := fmt.Sprintf("%x", md5.Sum([]byte(user.AuthToken)))
	correctStr := fmt.Sprintf("%s%d", md5Token, *timestamp)
	correctAuthMsg := fmt.Sprintf("%x", md5.Sum([]byte(correctStr)))
	if correctAuthMsg != *authMsg {
		logger.Log.Println("incorrect auth from", user.Name, "with", *authMsg,
			"instead of", correctAuthMsg)
		return errorAS(StatusBadAuth, nil)
	}

	return core.Result{StatusOK, []interface{}{user.AuthToken, a.nowPlayingURL,
		a.submissionURL}, nil, true}
}
예제 #3
0
파일: core.go 프로젝트: cfstras/cfmedias
func ResultByError(err error) Result {
	if err == nil {
		return ResultOK
	} else {
		return Result{Status: StatusError, Error: errrs.New(err.Error())}
	}
}
예제 #4
0
파일: api.go 프로젝트: cfstras/cfmedias
// Fetches an argument from an ArgMap, used for single args
// Breaks and passes along the error given, if it is not nil.
// If the argument does not exist, the return value is nil.
// Parameter force can be used to return an error if the argument does not exist.
func GetArg(args core.ArgMap, arg string, force bool, err error) (*string, error) {
	if err != nil {
		return nil, err
	}
	value, ok := args[arg]
	if !ok || len(value) == 0 {
		if force {
			return nil, errrs.New("Argument '" + arg + "' missing!")
		}
		return nil, nil
	}
	if len(value) > 1 {
		return nil, errrs.New("argument " + arg + " cannot be supplied more than once!")
	}
	return &value[0], nil
}
예제 #5
0
파일: config.go 프로젝트: cfstras/cfmedias
func Load(configFile string) error {
	if loaded {
		return errrs.New("Config already loaded.")
	}
	loaded = true

	if _, err := os.Stat(configFile); os.IsNotExist(err) {
		log.Log.Println("Initializing new configuation.")
		return nil
	}

	b, err := ioutil.ReadFile(configFile)
	if err != nil {
		return err
	}
	var read Configuration
	err = json.Unmarshal(b, &read)
	if err != nil {
		return err
	}

	// convert loaded maps to their config structs
	for k := range read.Plugins {
		err := mapstructure.Decode(read.Plugins[k], Default.pluginTypes[k])
		if err != nil {
			log.Log.Fatalln("Error loading config for plugin", k, "-", err)
		}
		read.Plugins[k] = Default.pluginTypes[k]
	}

	Current = &read

	//TODO ensure minimal config is done
	return nil
}
예제 #6
0
파일: main.go 프로젝트: cfstras/cfmedias
func (s *Sync) Sync(targetPath string, convert bool) error {
	logger.Log.Println("getting data...")
	tracks, err := s.db.ListAll()
	if err != nil {
		return err
	}
	logger.Log.Println(len(tracks), "tracks found.")
	logger.Log.Println("indexing target...")

	cleanPath := path.Clean(targetPath)
	if cleanPath != "" {
		cleanPath += "/"
	}
	var targetFiles []file
	filepath.Walk(targetPath, func(filepath string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}
		filepath = path.Clean(filepath)
		if !strings.HasPrefix(filepath, cleanPath) {
			return errrs.New("file " + filepath + " is not within " + cleanPath)
		}
		filepath = strings.TrimPrefix(filepath, cleanPath)
		targetFiles = append(targetFiles, file{filepath, info})
		return nil
	})
	logger.Log.Println(len(targetFiles), "files in target")

	return core.ErrorNotImplemented
}
예제 #7
0
파일: login.go 프로젝트: cfstras/cfmedias
// Checks a given authentication token agains the database.
// On success, returns the permission level of the user and their ID
// On failure, returns (AuthGuest, nil, error)
func (db *DB) Authenticate(authtoken string) (core.AuthLevel, *int64, error) {
	invalidErr := errrs.New("auth token invalid")
	user := User{}
	err := db.db.Find(&user, "auth_token = ?", authtoken).Error
	if err != nil || user.Id == 0 { // not found
		log.Log.Println("authToken not found", err)
		return core.AuthGuest, nil, invalidErr
	}
	return user.AuthLevel, &user.Id, nil
}
예제 #8
0
파일: api.go 프로젝트: cfstras/cfmedias
// Converts a *string to a boolean.
// Passes along errrs, if not nil.
func CastBool(arg *string, err error) (*bool, error) {
	if err != nil {
		return nil, err
	}
	if arg == nil {
		return nil, nil
	}
	casted, err := strconv.ParseBool(*arg)
	if err != nil {
		return nil, errrs.New(*arg + " is not boolean")
	}
	return &casted, nil
}
예제 #9
0
파일: api.go 프로젝트: cfstras/cfmedias
// Converts a *string to a uint64.
// Passes along errrs, if not nil.
func CastInt64(arg *string, err error) (*int64, error) {
	if err != nil {
		return nil, err
	}
	if arg == nil {
		return nil, nil
	}
	casted, err := strconv.ParseInt(*arg, 10, 64)
	if err != nil {
		return nil, errrs.New(*arg + " is not an integer")
	}
	return &casted, nil
}
예제 #10
0
파일: api.go 프로젝트: cfstras/cfmedias
// Converts a *string to a float32.
// Passes along errrs, if not nil.
func CastFloat(arg *string, err error) (*float32, error) {
	if err != nil {
		return nil, err
	}
	if arg == nil {
		return nil, nil
	}
	casted, err := strconv.ParseFloat(*arg, 32)
	if err != nil {
		return nil, errrs.New(*arg + " is not a floating point")
	}
	smaller := float32(casted)
	return &smaller, nil
}
예제 #11
0
파일: api.go 프로젝트: cfstras/cfmedias
// Converts a *string to a uint.
// Passes along errrs, if not nil.
func CastUint(arg *string, err error) (*uint, error) {
	if err != nil {
		return nil, err
	}
	if arg == nil {
		return nil, nil
	}
	casted, err := strconv.ParseUint(*arg, 10, 32)
	if err != nil {
		return nil, errrs.New(*arg + " is not an integer")
	}
	smaller := uint(casted)
	return &smaller, nil
}
예제 #12
0
파일: config.go 프로젝트: cfstras/cfmedias
func Save(configFile string) error {
	if Current == nil {
		return errrs.New("Config has not been initialized, cannot save!")
	}

	str, err := json.MarshalIndent(Current, "", "  ")
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(configFile, str, 0644)

	if err != nil {
		return err
	}

	return nil
}
예제 #13
0
파일: db.go 프로젝트: cfstras/cfmedias
func (d *DB) Open(c core.Core) error {
	if d.open {
		return errrs.New("DB is already opened!")
	}
	d.c = c

	var err error
	file := config.Current.DbFile

	d.db, err = gorm.Open("sqlite3", file)
	if err != nil {
		return err
	}
	d.open = true

	d.db.LogMode(true)
	if err := d.checkTables(); err != nil {
		return err
	}

	err = d.checkSanity()
	if err != nil {
		//TODO what now?
		return err
	}

	d.initStats(c)
	d.initLogic(c) // hear the difference?
	d.initLogin(c) // it's subtle but it could save your life
	d.initList(c)

	c.RegisterCommand(core.Command{
		[]string{"rescan"},
		"Refreshes the database by re-scanning the media folder.",
		map[string]string{},
		core.AuthAdmin,
		func(_ core.CommandContext) core.Result {
			go d.Update()
			return core.ResultOK
		}})

	return nil
}
예제 #14
0
파일: login.go 프로젝트: cfstras/cfmedias
func (db *DB) Login(name string, password string) (success bool,
	authToken string, err error) {
	user := User{}
	err = db.db.Where("name = ?", name).Find(&user).Error
	if err != nil {
		return false, "", errrs.New(err.Error())
	}
	if user.Id == 0 {
		return false, "", nil
	}
	salt := user.Password[:SaltSize]
	expected := user.Password[SaltSize:]
	hashedPassword := HashPassword([]byte(password), salt)
	if 1 == subtle.ConstantTimeCompare(expected, hashedPassword) {
		//TODO add option to create new authtoken to logout all clients
		return true, user.AuthToken, nil
	}
	return false, "", nil
}
예제 #15
0
func (up *updater) step(file string, info os.FileInfo, err error) error {
	if info == nil ||
		info.Name() == "." ||
		info.Name() == ".." {
		return nil
	}
	if info.IsDir() && info.Name() == ".git" {
		return filepath.SkipDir
	}
	select {
	case sig, ok := <-up.job:
		if !ok || sig >= core.SignalTerminate {
			log.Log.Println("Terminate got, processing remaining files")
			return ErrorTerminate
		}
	default:
	}

	if info.IsDir() {
		//log.Log.Println("in", file)
	} else if linked, err := filepath.EvalSymlinks(file); err != nil || file != linked {
		if err != nil {
			log.Log.Println("Error walking files:", err)
			return nil
		}
		//TODO add loop detection
		err = filepath.Walk(linked, up.step)
		if err != nil {
			return err
		}
	} else if !strings.HasPrefix(info.Name(), ".") {
		select {
		case <-up.stopStepping:
			return errors.New("aborting")
		case up.allFiles <- entry{path.Dir(file), info.Name()}:
		}
	}
	return nil
}
예제 #16
0
파일: login.go 프로젝트: cfstras/cfmedias
func (db *DB) CreateUser(name string, email string, authLevel core.AuthLevel,
	password string) (*User, error) {

	if !IsSafe(name, TypeUsername) {
		return nil, errrs.New(`Username is invalid. Can contain a-z, 0-9,
underscore and dash, minimum 3 characters.`)
	}
	if !IsSafe(email, TypeEmail) {
		return nil, errrs.New("EMail is invalid.")
	}
	if !IsSafe(password, TypePassword) {
		return nil, errrs.New(`Password is invalid. Must be at least 6 and at
most 128 characters.`)
	}

	// check for unique
	var num uint64
	err := db.db.Table(UserTable).Where("name = ?", name).Count(&num).Error
	if err != nil {
		return nil, errrs.New("Error checking username: "******"User already exists!")
	}

	user := User{Name: name, Email: email, AuthLevel: authLevel}

	// create authtoken
	user.AuthToken, err = db.makeAuthToken()
	if err != nil {
		return nil, err
	}

	// hash password
	user.Password = MakePassword([]byte(password))

	// store user
	err = db.db.Save(&user).Error

	if err != nil {
		return nil, errrs.New("Could not insert user: " + err.Error())
	}

	return &user, nil
}
예제 #17
0
파일: core.go 프로젝트: cfstras/cfmedias
	SignalNone JobSignal = iota
	SignalTerminate
	SignalKill
)

type Result struct {
	Status Status      `json:"status"`
	Result interface{} `json:"result,omitempty"`
	Error  error       `json:"error,omitempty"`
	IsRaw  bool        `json:"-"`
}

var (
	ResultOK = Result{Status: StatusOK, Result: nil, Error: nil}

	ErrorCmdNotFound    = errrs.New("Command not found!")
	ErrorNotAllowed     = errrs.New("You are not allowed to do that!")
	ErrorNotLoggedIn    = errrs.New("You are not allowed to do that; you need to be logged in!")
	ErrorNotImplemented = errrs.New("Sorry, this feature is not implemented yet.")
	ErrorUserNotFound   = errrs.New("User not found!")
	ErrorInvalidQuery   = errrs.New("Invalid Query!")

	//ErrorItemNotFound = errrs.New("The requested item was not found.")
)

func ResultByError(err error) Result {
	if err == nil {
		return ResultOK
	} else {
		return Result{Status: StatusError, Error: errrs.New(err.Error())}
	}
예제 #18
0
	success      chan bool
	stopStepping chan bool
	vlc          *vlc.VLC

	// the receiving goroutine shall increment these
	numAllFiles      int
	numNewFiles      int
	numImportFiles   int
	numInvalidFiles  int
	numFailedFiles   int
	numImportedFiles int
}

var Types = []string{"mp3", "wav", "flac", "m4a", "aac"}

var ErrorTerminate error = errors.New("Terminating")

func (d *DB) Update() {
	// keep file base up to date
	searchPath := config.Current.MediaPath
	if strings.Contains(searchPath, "~") {
		user, err := osuser.Current()
		if err != nil {
			log.Log.Println("Error getting user home directory:", err)
			return
		}
		searchPath = strings.Replace(searchPath, "~", user.HomeDir, -1)
	}
	if _, err := os.Stat(searchPath); os.IsNotExist(err) {
		log.Log.Println("Error: Music path", searchPath, "does not exist!")
		return
예제 #19
0
파일: db.go 프로젝트: cfstras/cfmedias
func (d *DB) Close() error {
	if !d.open {
		return errrs.New("DB is not open!")
	}
	return d.db.Close()
}
예제 #20
0
func (p *IPod) Sync(mountpoint string) error {
	logger.Log.Println("getting data...")
	tracks, err := p.db.ListAll()
	if err != nil {
		return err
	}
	logger.Log.Println(len(tracks), "tracks found.")
	logger.Log.Println("indexing target...")

	ipodDb, err := gpod.New(mountpoint)
	if err != nil {
		return err
	}
	ipodTracks := ipodDb.Tracks()
	logger.Log.Println(len(ipodTracks), "tracks on iPod.")

	idxFuncGpod := func(t gpod.Track) string {
		return t.Title() + Seperator + t.Album() + Seperator + t.Artist()
	}
	idxFunc := func(t db.Item) string {
		str := t.Title + Seperator
		if t.Album.Valid {
			str += t.Album.String
		}
		return str + Seperator + t.Artist
	}
	idx := make(map[string]gpod.Track)
	for _, t := range ipodTracks {
		idx[idxFuncGpod(t)] = t
	}
	var tracksMissing []*db.Item
	// cfmedias db id -> gpod track
	tracksFound := make(map[int64]gpod.Track)
	var tracksUnmatched []gpod.Track
	matched := make(map[string]bool)
	for i := range tracks {
		t := &tracks[i]
		match, ok := idx[idxFunc(*t)]
		if !ok {
			tracksMissing = append(tracksMissing, t)
			continue
		}
		matched[idxFunc(*t)] = true
		tracksFound[t.Id] = match
	}
	for i := range ipodTracks {
		t := &ipodTracks[i]
		if !matched[idxFuncGpod(*t)] {
			tracksUnmatched = append(tracksUnmatched, *t)
		}
	}
	logger.Log.Println(len(tracksFound), "tracks found,",
		len(tracksUnmatched), "unknown tracks on iPod,",
		len(tracksMissing), "tracks to copy")
	m := map[string]interface{}{
		"unmatched": tracksUnmatched,
		"missing":   tracksMissing,
	}
	b, err := json.MarshalIndent(m, "", "  ")
	if err != nil {
		return err
	}
	err = ioutil.WriteFile("sync-info.json", b, 0644)
	if err != nil {
		return err
	}
	logger.Log.Println("Wrote sync-info.json.")

	//TODO update tags

	// delete unmatched
	deleteErrors := make(map[gpod.Track]error)
	for _, t := range tracksUnmatched {
		logger.Log.Println("Deleting", t)
		err := t.Delete()
		if err != nil {
			logger.Log.Println("Error deleting", t, err)
			deleteErrors[t] = err
			continue
		}
	}

	// add missing
	copyErrors := make(map[*db.Item]error)
	for _, t := range tracksMissing {
		logger.Log.Println("Adding", t)
		ipodT := gpod.NewTrack(ipodDb)
		ipodT.SetTitle(t.Title)
		ipodT.SetAlbum(db.StrStr(t.Album))
		ipodT.SetArtist(t.Artist)
		ipodT.SetGenre(db.StrStr(t.Genre))
		//ipodT.SetFiletype(t.Filename)
		//ipodT.SetComment(t.Comment)
		//ipodT.SetComposer(t.Composer)
		//ipodT.SetDescription(t.Description)
		ipodT.SetAlbumartist(db.StrStr(t.AlbumArtist))
		//ipodT.SetSize(t.Size)
		//ipodT.SetLength(t.Length)

		//TODO add missing fields
		// don't make sense yet
		//ipodT.SetRating(t.Rating())
		//ipodT.SetPlaycount(t.Playcount)
		ipodT.SetTimeAdded(t.CreatedAt)

		//TODO playlist hierarchy

		path := t.Path()
		if path == nil {
			err := errrs.New("No file to copy")
			logger.Log.Println("Error finding", t, err)
			copyErrors[t] = err
			continue
		}
		ipodDb.Add(ipodT)
		ipodDb.MPL().Add(ipodT)
		err := ipodDb.Copy(ipodT, *path)
		if err != nil {
			logger.Log.Println("Error copying", t, err)
			copyErrors[t] = err
			ipodDb.MPL().Remove(ipodT)
			ipodDb.Remove(ipodT)
		}
	}
	err = ipodDb.Save()
	if err != nil {
		return err
	}
	// give back other errors, if any
	if len(copyErrors) > 0 {
		serr := &syncError{deleteErrors, copyErrors}
		return serr
	}
	return nil
}
예제 #21
0
func (p *IPod) Sync(mountpoint string) error {
	return errrs.New("iPod sync not supported on Windows. I'm sorry.")
}
예제 #22
0
파일: login.go 프로젝트: cfstras/cfmedias
func (db *DB) initLogin(c core.Core) {
	c.RegisterCommand(core.Command{
		[]string{"create_user"},
		"Creates a user in the database",
		map[string]string{
			"name":  "Username",
			"email": "E-Mail",
			"auth_level": fmt.Sprintf("User Rank: Guest(%d), User(%d), "+
				"Admin(%d), Root(%d)", core.AuthGuest, core.AuthUser,
				core.AuthAdmin, core.AuthRoot),
			"password": "******"},
		core.AuthAdmin,
		func(ctx core.CommandContext) core.Result {
			args := ctx.Args
			var err error
			name, err := util.GetArg(args, "name", true, err)
			email, err := util.GetArg(args, "email", true, err)
			authLevelS, err := util.GetArg(args, "auth_level", true, err)
			password, err := util.GetArg(args, "password", true, err)

			authLevelI, err := util.CastUint(authLevelS, err)

			if err != nil {
				return core.Result{Status: core.StatusError, Error: err}
			}
			authLevel := core.AuthLevel(*authLevelI)

			if authLevel >= ctx.AuthLevel {
				return core.ResultByError(errrs.New("You cannot create a user" +
					" with that level!"))
			}

			user, err := db.CreateUser(*name, *email, authLevel, *password)
			if err == nil {
				return core.Result{Status: core.StatusOK, Result: user}
			}
			return core.Result{Status: core.StatusError, Error: err}
		}})

	c.RegisterCommand(core.Command{
		[]string{"login"},
		"Logs in with user/password and returns the auth token",
		map[string]string{
			"name":     "Username",
			"password": "******"},
		core.AuthGuest,
		func(ctx core.CommandContext) core.Result {
			args := ctx.Args
			var err error
			name, err := util.GetArg(args, "name", true, err)
			password, err := util.GetArg(args, "password", true, err)

			if err != nil {
				return core.ResultByError(err)
			}

			success, authToken, err := db.Login(*name, *password)
			if err == nil && success {
				return core.Result{Status: core.StatusOK,
					Result: map[string]string{"auth_token": authToken}}
			}
			if err == nil {
				return core.Result{Status: core.StatusError,
					Error: errrs.New("Wrong username or password")}
			}
			return core.Result{Status: core.StatusError, Error: err}
		}})

	c.RegisterCommand(core.Command{
		[]string{"change_authtoken", "logout"},
		`Changes the authentication token of the user, thereby logging out all
clients`,
		map[string]string{
			"name": `(Optional) username of the user to log out. Leave empty to
use current user`,
		},
		core.AuthUser,
		func(ctx core.CommandContext) core.Result {
			args := ctx.Args
			var err error
			name, err := util.GetArg(args, "name", false, err)
			if err != nil {
				return core.ResultByError(err)
			}
			var user *User
			if name != nil {
				if ctx.AuthLevel < core.AuthAdmin {
					return core.ResultByError(core.ErrorNotAllowed)
				}
				user, err = db.GetUserByName(*name)
			} else {
				if ctx.UserId == nil {
					return core.ResultByError(core.ErrorNotLoggedIn)
				}
				user, err = db.GetUser(*ctx.UserId)
			}
			if user == nil {
				return core.ResultByError(core.ErrorUserNotFound)
			}
			_, err = db.ChangeAuthToken(user)
			return core.ResultByError(err)
		}})
}