service

package
v0.0.0-...-cbbc4cb Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 30, 2024 License: MIT Imports: 46 Imported by: 0

Documentation

Index

Constants

View Source
const (
	CacheIdKey      cacheKey = "cacheId"
	CacheQualityKey cacheKey = "cacheQuality"
	CachePageKey    cacheKey = "cachePageNum"
	CacheMediaKey   cacheKey = "cacheMedia"
)
View Source
const (
	HighresSize = 2500
	ThumbSize   = 500
)

Variables

This section is empty.

Functions

func ContentIdFromHash

func ContentIdFromHash(newHash hash.Hash) models.ContentId

func GenerateContentId

func GenerateContentId(f *fileTree.WeblensFileImpl) (models.ContentId, error)

func MakeUniqueChildName

func MakeUniqueChildName(parent *fileTree.WeblensFileImpl, childName string) string

func NewShareService

func NewShareService(collection *mongo.Collection) (models.ShareService, error)

Types

type AccessServiceImpl

type AccessServiceImpl struct {
	// contains filtered or unexported fields
}

func NewAccessService

func NewAccessService(userService models.UserService, col *mongo.Collection) (*AccessServiceImpl, error)

func (*AccessServiceImpl) AddApiKey

func (accSrv *AccessServiceImpl) AddApiKey(key models.ApiKey) error

func (*AccessServiceImpl) CanUserAccessAlbum

func (accSrv *AccessServiceImpl) CanUserAccessAlbum(
	user *models.User, album *models.Album,
	share *models.AlbumShare,
) bool

func (*AccessServiceImpl) CanUserAccessFile

func (accSrv *AccessServiceImpl) CanUserAccessFile(
	user *models.User, file *fileTree.WeblensFileImpl, share *models.FileShare,
) bool

func (*AccessServiceImpl) CanUserModifyShare

func (accSrv *AccessServiceImpl) CanUserModifyShare(user *models.User, share models.Share) bool

func (*AccessServiceImpl) DeleteApiKey

func (accSrv *AccessServiceImpl) DeleteApiKey(key models.WeblensApiKey) error

func (*AccessServiceImpl) GenerateApiKey

func (accSrv *AccessServiceImpl) GenerateApiKey(creator *models.User, local *models.Instance) (
	models.ApiKey, error,
)

func (*AccessServiceImpl) GenerateJwtToken

func (accSrv *AccessServiceImpl) GenerateJwtToken(user *models.User) (string, time.Time, error)

func (*AccessServiceImpl) GetAllKeys

func (accSrv *AccessServiceImpl) GetAllKeys(accessor *models.User) ([]models.ApiKey, error)

func (*AccessServiceImpl) GetAllKeysByServer

func (accSrv *AccessServiceImpl) GetAllKeysByServer(
	accessor *models.User, serverId models.InstanceId,
) ([]models.ApiKey, error)

func (*AccessServiceImpl) GetApiKey

func (accSrv *AccessServiceImpl) GetApiKey(key models.WeblensApiKey) (models.ApiKey, error)

func (*AccessServiceImpl) GetUserFromToken

func (accSrv *AccessServiceImpl) GetUserFromToken(tokenStr string) (*models.User, error)

func (*AccessServiceImpl) SetKeyUsedBy

func (accSrv *AccessServiceImpl) SetKeyUsedBy(key models.WeblensApiKey, remote *models.Instance) error

func (*AccessServiceImpl) Size

func (accSrv *AccessServiceImpl) Size() int

type AlbumServiceImpl

type AlbumServiceImpl struct {
	// contains filtered or unexported fields
}

func NewAlbumService

func NewAlbumService(
	col *mongo.Collection, mediaService models.MediaService, shareService models.ShareService,
) *AlbumServiceImpl

func (*AlbumServiceImpl) Add

func (as *AlbumServiceImpl) Add(a *models.Album) error

func (*AlbumServiceImpl) AddMediaToAlbum

func (as *AlbumServiceImpl) AddMediaToAlbum(album *models.Album, media ...*models.Media) error

func (*AlbumServiceImpl) Del

func (as *AlbumServiceImpl) Del(aId models.AlbumId) error

func (*AlbumServiceImpl) Get

func (as *AlbumServiceImpl) Get(aId models.AlbumId) *models.Album

func (*AlbumServiceImpl) GetAlbumMedias

func (as *AlbumServiceImpl) GetAlbumMedias(album *models.Album) iter.Seq[*models.Media]

func (*AlbumServiceImpl) GetAllByUser

func (as *AlbumServiceImpl) GetAllByUser(u *models.User) ([]*models.Album, error)

func (*AlbumServiceImpl) Init

func (as *AlbumServiceImpl) Init() error

func (*AlbumServiceImpl) RemoveMediaFromAlbum

func (as *AlbumServiceImpl) RemoveMediaFromAlbum(album *models.Album, mediaIds ...models.ContentId) error

func (*AlbumServiceImpl) RemoveMediaFromAny

func (as *AlbumServiceImpl) RemoveMediaFromAny(mediaId models.ContentId) error

func (*AlbumServiceImpl) RenameAlbum

func (as *AlbumServiceImpl) RenameAlbum(album *models.Album, newName string) error

func (*AlbumServiceImpl) SetAlbumCover

func (as *AlbumServiceImpl) SetAlbumCover(albumId models.AlbumId, cover *models.Media) error

func (*AlbumServiceImpl) Size

func (as *AlbumServiceImpl) Size() int

type ClientManager

type ClientManager struct {
	// contains filtered or unexported fields
}

func NewClientManager

func NewClientManager(
	pack *models.ServicePack,
) *ClientManager

func (*ClientManager) ClientConnect

func (cm *ClientManager) ClientConnect(conn *websocket.Conn, user *models.User) *models.WsClient

func (*ClientManager) ClientDisconnect

func (cm *ClientManager) ClientDisconnect(c *models.WsClient)

func (*ClientManager) FolderSubToTask

func (cm *ClientManager) FolderSubToTask(folderId fileTree.FileId, taskId task.Id)

func (*ClientManager) GetAllClients

func (cm *ClientManager) GetAllClients() []*models.WsClient

func (*ClientManager) GetClientByServerId

func (cm *ClientManager) GetClientByServerId(instanceId models.InstanceId) *models.WsClient

func (*ClientManager) GetClientByUsername

func (cm *ClientManager) GetClientByUsername(username models.Username) *models.WsClient

func (*ClientManager) GetConnectedAdmins

func (cm *ClientManager) GetConnectedAdmins() []*models.WsClient

func (*ClientManager) GetSubscribers

func (cm *ClientManager) GetSubscribers(st models.WsAction, key models.SubId) (clients []*models.WsClient)

func (*ClientManager) RemoteConnect

func (cm *ClientManager) RemoteConnect(conn *websocket.Conn, remote *models.Instance) *models.WsClient

func (*ClientManager) Send

func (cm *ClientManager) Send(msg models.WsResponseInfo)

func (*ClientManager) Subscribe

func (cm *ClientManager) Subscribe(
	c *models.WsClient, key models.SubId, action models.WsAction, subTime time.Time, share models.Share,
) (complete bool, results map[task.TaskResultKey]any, err error)

Subscribe links a websocket connection to a "key" that can be broadcast to later if relevant updates need to be communicated

Returns "true" and the results at meta.LookingFor if the task is completed, false and nil otherwise. Subscriptions to types that represent ongoing events like FolderSubscribe never return a truthy completed

func (*ClientManager) UnsubTask

func (cm *ClientManager) UnsubTask(taskId task.Id)

func (*ClientManager) Unsubscribe

func (cm *ClientManager) Unsubscribe(c *models.WsClient, key models.SubId, unSubTime time.Time) error

type FileServiceImpl

type FileServiceImpl struct {
	// contains filtered or unexported fields
}

func NewFileService

func NewFileService(
	instanceService models.InstanceService,
	userService models.UserService,
	accessService models.AccessService,
	mediaService models.MediaService,
	folderCoverCol *mongo.Collection,
	trees ...fileTree.FileTree,
) (*FileServiceImpl, error)

func (*FileServiceImpl) AddTask

func (fs *FileServiceImpl) AddTask(f *fileTree.WeblensFileImpl, t *task.Task) error

func (*FileServiceImpl) AddTree

func (fs *FileServiceImpl) AddTree(tree fileTree.FileTree)

func (*FileServiceImpl) CreateFile

func (fs *FileServiceImpl) CreateFile(parent *fileTree.WeblensFileImpl, filename string, event *fileTree.FileEvent, caster models.FileCaster) (
	*fileTree.WeblensFileImpl, error,
)

func (*FileServiceImpl) CreateFolder

func (fs *FileServiceImpl) CreateFolder(parent *fileTree.WeblensFileImpl, folderName string, event *fileTree.FileEvent, caster models.FileCaster) (
	*fileTree.WeblensFileImpl,
	error,
)

func (*FileServiceImpl) CreateUserHome

func (fs *FileServiceImpl) CreateUserHome(user *models.User) error

func (*FileServiceImpl) DeleteCacheFile

func (fs *FileServiceImpl) DeleteCacheFile(f fileTree.WeblensFile) error

func (*FileServiceImpl) DeleteFiles

func (fs *FileServiceImpl) DeleteFiles(
	files []*fileTree.WeblensFileImpl, treeName string, caster models.FileCaster,
) error

DeleteFiles removes files being pointed to from the tree and moves them to the restore tree

func (*FileServiceImpl) GetFileByContentId

func (fs *FileServiceImpl) GetFileByContentId(contentId models.ContentId) (*fileTree.WeblensFileImpl, error)

func (*FileServiceImpl) GetFileByTree

func (fs *FileServiceImpl) GetFileByTree(id fileTree.FileId, treeAlias string) (*fileTree.WeblensFileImpl, error)

func (*FileServiceImpl) GetFileOwner

func (fs *FileServiceImpl) GetFileOwner(file *fileTree.WeblensFileImpl) *models.User

func (*FileServiceImpl) GetFileSafe

func (fs *FileServiceImpl) GetFileSafe(id fileTree.FileId, user *models.User, share *models.FileShare) (
	*fileTree.WeblensFileImpl,
	error,
)

func (*FileServiceImpl) GetFileTreeByName

func (fs *FileServiceImpl) GetFileTreeByName(treeName string) fileTree.FileTree

func (*FileServiceImpl) GetFiles

func (*FileServiceImpl) GetFolderCover

func (fs *FileServiceImpl) GetFolderCover(folder *fileTree.WeblensFileImpl) (models.ContentId, error)

func (*FileServiceImpl) GetJournalByTree

func (fs *FileServiceImpl) GetJournalByTree(treeName string) fileTree.Journal

func (*FileServiceImpl) GetMediaCacheByFilename

func (fs *FileServiceImpl) GetMediaCacheByFilename(thumbFileName string) (*fileTree.WeblensFileImpl, error)

func (*FileServiceImpl) GetTasks

func (fs *FileServiceImpl) GetTasks(f *fileTree.WeblensFileImpl) []*task.Task

func (*FileServiceImpl) GetZip

func (*FileServiceImpl) IsFileInTrash

func (fs *FileServiceImpl) IsFileInTrash(f *fileTree.WeblensFileImpl) bool

func (*FileServiceImpl) MoveFiles

func (fs *FileServiceImpl) MoveFiles(
	files []*fileTree.WeblensFileImpl, destFolder *fileTree.WeblensFileImpl, treeName string, caster models.FileCaster,
) error

func (*FileServiceImpl) MoveFilesToTrash

func (fs *FileServiceImpl) MoveFilesToTrash(
	files []*fileTree.WeblensFileImpl, user *models.User, share *models.FileShare, caster models.FileCaster,
) error

func (*FileServiceImpl) NewBackupFile

func (fs *FileServiceImpl) NewBackupFile(lt *fileTree.Lifetime) (*fileTree.WeblensFileImpl, error)

func (*FileServiceImpl) NewCacheFile

func (fs *FileServiceImpl) NewCacheFile(
	media *models.Media, quality models.MediaQuality, pageNum int,
) (*fileTree.WeblensFileImpl, error)

func (*FileServiceImpl) NewZip

func (fs *FileServiceImpl) NewZip(zipName string, owner *models.User) (*fileTree.WeblensFileImpl, error)

func (*FileServiceImpl) PathToFile

func (fs *FileServiceImpl) PathToFile(searchPath string) (*fileTree.WeblensFileImpl, error)

func (*FileServiceImpl) ReadFile

func (*FileServiceImpl) RemoveTask

func (fs *FileServiceImpl) RemoveTask(f *fileTree.WeblensFileImpl, t *task.Task) error

func (*FileServiceImpl) RenameFile

func (fs *FileServiceImpl) RenameFile(file *fileTree.WeblensFileImpl, newName string, caster models.FileCaster) error

func (*FileServiceImpl) ResizeDown

func (fs *FileServiceImpl) ResizeDown(f *fileTree.WeblensFileImpl, caster models.FileCaster) error

func (*FileServiceImpl) ResizeUp

func (*FileServiceImpl) RestoreFiles

func (fs *FileServiceImpl) RestoreFiles(
	ids []fileTree.FileId, newParent *fileTree.WeblensFileImpl, restoreTime time.Time, caster models.FileCaster,
) error

func (*FileServiceImpl) RestoreHistory

func (fs *FileServiceImpl) RestoreHistory(lifetimes []*fileTree.Lifetime) error

func (*FileServiceImpl) ReturnFilesFromTrash

func (fs *FileServiceImpl) ReturnFilesFromTrash(
	trashFiles []*fileTree.WeblensFileImpl, c models.FileCaster,
) error

func (*FileServiceImpl) SetFolderCover

func (fs *FileServiceImpl) SetFolderCover(folderId fileTree.FileId, coverId models.ContentId) error

func (*FileServiceImpl) SetMediaService

func (fs *FileServiceImpl) SetMediaService(mediaService *MediaServiceImpl)

func (*FileServiceImpl) Size

func (fs *FileServiceImpl) Size(treeAlias string) int64

func (*FileServiceImpl) UserPathToFile

func (fs *FileServiceImpl) UserPathToFile(searchPath string, user *models.User) (*fileTree.WeblensFileImpl, error)

type FolderCoverPair

type FolderCoverPair struct {
	FolderId  fileTree.FileId  `bson:"folderId"`
	ContentId models.ContentId `bson:"coverId"`
}

type InstanceServiceImpl

type InstanceServiceImpl struct {
	// contains filtered or unexported fields
}

func NewInstanceService

func NewInstanceService(col database.MongoCollection) (*InstanceServiceImpl, error)

func (*InstanceServiceImpl) Add

func (*InstanceServiceImpl) AttachRemoteCore

func (is *InstanceServiceImpl) AttachRemoteCore(coreAddr string, key string) (*models.Instance, error)

func (*InstanceServiceImpl) Del

func (*InstanceServiceImpl) Get

func (is *InstanceServiceImpl) Get(dbId string) *models.Instance

func (*InstanceServiceImpl) GetAllByOriginServer

func (is *InstanceServiceImpl) GetAllByOriginServer(originId models.InstanceId) []*models.Instance

func (*InstanceServiceImpl) GetByInstanceId

func (is *InstanceServiceImpl) GetByInstanceId(serverId models.InstanceId) *models.Instance

func (*InstanceServiceImpl) GetCores

func (is *InstanceServiceImpl) GetCores() []*models.Instance

func (*InstanceServiceImpl) GetLocal

func (is *InstanceServiceImpl) GetLocal() *models.Instance

func (*InstanceServiceImpl) GetRemotes

func (is *InstanceServiceImpl) GetRemotes() []*models.Instance

GetRemotes returns all instances that are not the local server

func (*InstanceServiceImpl) InitBackup

func (is *InstanceServiceImpl) InitBackup(
	name, coreAddr string, key models.WeblensApiKey,
) error

func (*InstanceServiceImpl) InitCore

func (is *InstanceServiceImpl) InitCore(serverName string) error

func (*InstanceServiceImpl) ResetAll

func (is *InstanceServiceImpl) ResetAll() error

ResetAll will clear all known servers, including the local one, and will reset this server to initialization mode.

func (*InstanceServiceImpl) SetLastBackup

func (is *InstanceServiceImpl) SetLastBackup(id models.InstanceId, lastBackup time.Time) error

func (*InstanceServiceImpl) Size

func (is *InstanceServiceImpl) Size() int

type MediaServiceImpl

type MediaServiceImpl struct {
	AlbumService models.AlbumService
	// contains filtered or unexported fields
}

func NewMediaService

func NewMediaService(
	fileService models.FileService, mediaTypeServ models.MediaTypeService, albumService models.AlbumService,
	col *mongo.Collection,
) (*MediaServiceImpl, error)

func (*MediaServiceImpl) Add

func (ms *MediaServiceImpl) Add(m *models.Media) error

func (*MediaServiceImpl) AddFileToMedia

func (ms *MediaServiceImpl) AddFileToMedia(m *models.Media, f *fileTree.WeblensFileImpl) error

func (*MediaServiceImpl) AdjustMediaDates

func (ms *MediaServiceImpl) AdjustMediaDates(
	anchor *models.Media, newTime time.Time, extraMedias []*models.Media,
) error

func (*MediaServiceImpl) Cleanup

func (ms *MediaServiceImpl) Cleanup() error

func (*MediaServiceImpl) Del

func (ms *MediaServiceImpl) Del(cId models.ContentId) error

func (*MediaServiceImpl) FetchCacheImg

func (ms *MediaServiceImpl) FetchCacheImg(m *models.Media, q models.MediaQuality, pageNum int) ([]byte, error)

func (*MediaServiceImpl) Get

func (*MediaServiceImpl) GetAll

func (ms *MediaServiceImpl) GetAll() []*models.Media

func (*MediaServiceImpl) GetFilteredMedia

func (ms *MediaServiceImpl) GetFilteredMedia(
	requester *models.User, sort string, sortDirection int, excludeIds []models.ContentId,
	allowRaw bool, allowHidden bool,
) ([]*models.Media, error)

func (*MediaServiceImpl) GetMediaType

func (ms *MediaServiceImpl) GetMediaType(m *models.Media) models.MediaType

func (*MediaServiceImpl) GetMediaTypes

func (ms *MediaServiceImpl) GetMediaTypes() models.MediaTypeService

func (*MediaServiceImpl) GetProminentColors

func (ms *MediaServiceImpl) GetProminentColors(media *models.Media) (prom []string, err error)

func (*MediaServiceImpl) HideMedia

func (ms *MediaServiceImpl) HideMedia(m *models.Media, hidden bool) error

func (*MediaServiceImpl) IsCached

func (ms *MediaServiceImpl) IsCached(m *models.Media) bool

func (*MediaServiceImpl) IsFileDisplayable

func (ms *MediaServiceImpl) IsFileDisplayable(f *fileTree.WeblensFileImpl) bool

func (*MediaServiceImpl) LoadMediaFromFile

func (ms *MediaServiceImpl) LoadMediaFromFile(m *models.Media, file *fileTree.WeblensFileImpl) error

func (*MediaServiceImpl) NukeCache

func (ms *MediaServiceImpl) NukeCache() error

func (*MediaServiceImpl) RecursiveGetMedia

func (ms *MediaServiceImpl) RecursiveGetMedia(folders ...*fileTree.WeblensFileImpl) []*models.Media

func (*MediaServiceImpl) RemoveFileFromMedia

func (ms *MediaServiceImpl) RemoveFileFromMedia(media *models.Media, fileId fileTree.FileId) error

func (*MediaServiceImpl) SetMediaLiked

func (ms *MediaServiceImpl) SetMediaLiked(mediaId models.ContentId, liked bool, username models.Username) error

func (*MediaServiceImpl) Size

func (ms *MediaServiceImpl) Size() int

func (*MediaServiceImpl) StreamCacheVideo

func (ms *MediaServiceImpl) StreamCacheVideo(m *models.Media, startByte, endByte int) ([]byte, error)

func (*MediaServiceImpl) StreamVideo

func (ms *MediaServiceImpl) StreamVideo(
	m *models.Media, u *models.User, share *models.FileShare,
) (*models.VideoStreamer, error)

func (*MediaServiceImpl) TypeService

func (ms *MediaServiceImpl) TypeService() models.MediaTypeService

type ShareServiceImpl

type ShareServiceImpl struct {
	// contains filtered or unexported fields
}

func (*ShareServiceImpl) Add

func (ss *ShareServiceImpl) Add(sh models.Share) error

func (*ShareServiceImpl) AddUsers

func (ss *ShareServiceImpl) AddUsers(share models.Share, newUsers []*models.User) error

func (*ShareServiceImpl) Del

func (ss *ShareServiceImpl) Del(sId models.ShareId) error

func (*ShareServiceImpl) EnableShare

func (ss *ShareServiceImpl) EnableShare(share models.Share, enable bool) error

func (*ShareServiceImpl) Get

func (*ShareServiceImpl) GetAlbumShare

func (ss *ShareServiceImpl) GetAlbumShare(aId models.AlbumId) (*models.AlbumShare, error)

func (*ShareServiceImpl) GetAlbumSharesWithUser

func (ss *ShareServiceImpl) GetAlbumSharesWithUser(u *models.User) ([]*models.AlbumShare, error)

func (*ShareServiceImpl) GetAllShares

func (ss *ShareServiceImpl) GetAllShares() []models.Share

func (*ShareServiceImpl) GetFileShare

func (ss *ShareServiceImpl) GetFileShare(fId fileTree.FileId) (*models.FileShare, error)

func (*ShareServiceImpl) GetFileSharesWithUser

func (ss *ShareServiceImpl) GetFileSharesWithUser(u *models.User) ([]*models.FileShare, error)

func (*ShareServiceImpl) RemoveUsers

func (ss *ShareServiceImpl) RemoveUsers(share models.Share, removeUsers []*models.User) error

func (*ShareServiceImpl) SetSharePublic

func (ss *ShareServiceImpl) SetSharePublic(share models.Share, public bool) error

func (*ShareServiceImpl) Size

func (ss *ShareServiceImpl) Size() int

type TrashEntry

type TrashEntry struct {
	OrigParent   fileTree.FileId `bson:"originalParentId"`
	OrigFilename string          `bson:"originalFilename"`
	FileId       fileTree.FileId `bson:"fileId"`
}

type UserServiceImpl

type UserServiceImpl struct {
	// contains filtered or unexported fields
}

func NewUserService

func NewUserService(col database.MongoCollection) (*UserServiceImpl, error)

func (*UserServiceImpl) ActivateUser

func (us *UserServiceImpl) ActivateUser(u *models.User, active bool) error

func (*UserServiceImpl) Add

func (us *UserServiceImpl) Add(user *models.User) error

func (*UserServiceImpl) CreateOwner

func (us *UserServiceImpl) CreateOwner(username, password string) (*models.User, error)

func (*UserServiceImpl) Del

func (us *UserServiceImpl) Del(un models.Username) error

func (*UserServiceImpl) Get

func (us *UserServiceImpl) Get(username models.Username) *models.User

func (*UserServiceImpl) GetAll

func (us *UserServiceImpl) GetAll() (iter.Seq[*models.User], error)

func (*UserServiceImpl) GetPublicUser

func (us *UserServiceImpl) GetPublicUser() *models.User

func (*UserServiceImpl) GetRootUser

func (us *UserServiceImpl) GetRootUser() *models.User

func (*UserServiceImpl) SearchByUsername

func (us *UserServiceImpl) SearchByUsername(searchString string) (iter.Seq[*models.User], error)

func (*UserServiceImpl) SetUserAdmin

func (us *UserServiceImpl) SetUserAdmin(u *models.User, admin bool) error

func (*UserServiceImpl) Size

func (us *UserServiceImpl) Size() int

func (*UserServiceImpl) UpdateUserHome

func (us *UserServiceImpl) UpdateUserHome(u *models.User) error

func (*UserServiceImpl) UpdateUserPassword

func (us *UserServiceImpl) UpdateUserPassword(
	username models.Username, oldPassword, newPassword string,
	allowEmptyOld bool,
) error

type WebdavFs

type WebdavFs struct {
	WeblensFs models.FileService
	Caster    models.FileCaster
}

func (WebdavFs) Mkdir

func (w WebdavFs) Mkdir(ctx context.Context, name string, perm os.FileMode) error

func (WebdavFs) OpenFile

func (w WebdavFs) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error)

func (WebdavFs) RemoveAll

func (w WebdavFs) RemoveAll(ctx context.Context, name string) error

func (WebdavFs) Rename

func (w WebdavFs) Rename(ctx context.Context, oldName, newName string) error

func (WebdavFs) Stat

func (w WebdavFs) Stat(ctx context.Context, name string) (os.FileInfo, error)

type WlClaims

type WlClaims struct {
	Username string `json:"username"`
	jwt.RegisteredClaims
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL