diff --git a/_third_party/code.google.com/p/google-api-go-client/drive/v2/drive-api.json b/_third_party/code.google.com/p/google-api-go-client/drive/v2/drive-api.json deleted file mode 100644 index b2a34c7..0000000 --- a/_third_party/code.google.com/p/google-api-go-client/drive/v2/drive-api.json +++ /dev/null @@ -1,4059 +0,0 @@ -{ - "kind": "discovery#restDescription", - "etag": "\"l66ggWbucbkBw9Lpos72oziyefE/8QMIKXQb4gLNnKU_aYZeGRdMS08\"", - "discoveryVersion": "v1", - "id": "drive:v2", - "name": "drive", - "version": "v2", - "revision": "20141024", - "title": "Drive API", - "description": "The API to interact with Drive.", - "ownerDomain": "google.com", - "ownerName": "Google", - "icons": { - "x16": "https://ssl.gstatic.com/docs/doclist/images/drive_icon_16.png", - "x32": "https://ssl.gstatic.com/docs/doclist/images/drive_icon_32.png" - }, - "documentationLink": "https://developers.google.com/drive/", - "protocol": "rest", - "baseUrl": "https://www.googleapis.com/drive/v2/", - "basePath": "/drive/v2/", - "rootUrl": "https://www.googleapis.com/", - "servicePath": "drive/v2/", - "batchPath": "batch", - "parameters": { - "alt": { - "type": "string", - "description": "Data format for the response.", - "default": "json", - "enum": [ - "json" - ], - "enumDescriptions": [ - "Responses with Content-Type of application/json" - ], - "location": "query" - }, - "fields": { - "type": "string", - "description": "Selector specifying which fields to include in a partial response.", - "location": "query" - }, - "key": { - "type": "string", - "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.", - "location": "query" - }, - "oauth_token": { - "type": "string", - "description": "OAuth 2.0 token for the current user.", - "location": "query" - }, - "prettyPrint": { - "type": "boolean", - "description": "Returns response with indentations and line breaks.", - "default": "true", - "location": "query" - }, - "quotaUser": { - "type": "string", - "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.", - "location": "query" - }, - "userIp": { - "type": "string", - "description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.", - "location": "query" - } - }, - "auth": { - "oauth2": { - "scopes": { - "https://www.googleapis.com/auth/drive": { - "description": "View and manage the files and documents in your Google Drive" - }, - "https://www.googleapis.com/auth/drive.appdata": { - "description": "View and manage its own configuration data in your Google Drive" - }, - "https://www.googleapis.com/auth/drive.apps.readonly": { - "description": "View your Google Drive apps" - }, - "https://www.googleapis.com/auth/drive.file": { - "description": "View and manage Google Drive files that you have opened or created with this app" - }, - "https://www.googleapis.com/auth/drive.metadata.readonly": { - "description": "View metadata for files and documents in your Google Drive" - }, - "https://www.googleapis.com/auth/drive.readonly": { - "description": "View the files and documents in your Google Drive" - }, - "https://www.googleapis.com/auth/drive.scripts": { - "description": "Modify your Google Apps Script scripts' behavior" - } - } - } - }, - "schemas": { - "About": { - "id": "About", - "type": "object", - "description": "An item with user information and settings.", - "properties": { - "additionalRoleInfo": { - "type": "array", - "description": "Information about supported additional roles per file type. The most specific type takes precedence.", - "items": { - "type": "object", - "properties": { - "roleSets": { - "type": "array", - "description": "The supported additional roles per primary role.", - "items": { - "type": "object", - "properties": { - "additionalRoles": { - "type": "array", - "description": "The supported additional roles with the primary role.", - "items": { - "type": "string" - } - }, - "primaryRole": { - "type": "string", - "description": "A primary permission role." - } - } - } - }, - "type": { - "type": "string", - "description": "The content type that this additional role info applies to." - } - } - } - }, - "domainSharingPolicy": { - "type": "string", - "description": "The domain sharing policy for the current user." - }, - "etag": { - "type": "string", - "description": "The ETag of the item." - }, - "exportFormats": { - "type": "array", - "description": "The allowable export formats.", - "items": { - "type": "object", - "properties": { - "source": { - "type": "string", - "description": "The content type to convert from." - }, - "targets": { - "type": "array", - "description": "The possible content types to convert to.", - "items": { - "type": "string" - } - } - } - } - }, - "features": { - "type": "array", - "description": "List of additional features enabled on this account.", - "items": { - "type": "object", - "properties": { - "featureName": { - "type": "string", - "description": "The name of the feature." - }, - "featureRate": { - "type": "number", - "description": "The request limit rate for this feature, in queries per second.", - "format": "double" - } - } - } - }, - "importFormats": { - "type": "array", - "description": "The allowable import formats.", - "items": { - "type": "object", - "properties": { - "source": { - "type": "string", - "description": "The imported file's content type to convert from." - }, - "targets": { - "type": "array", - "description": "The possible content types to convert to.", - "items": { - "type": "string" - } - } - } - } - }, - "isCurrentAppInstalled": { - "type": "boolean", - "description": "A boolean indicating whether the authenticated app is installed by the authenticated user." - }, - "kind": { - "type": "string", - "description": "This is always drive#about.", - "default": "drive#about" - }, - "languageCode": { - "type": "string", - "description": "The user's language or locale code, as defined by BCP 47, with some extensions from Unicode's LDML format (http://www.unicode.org/reports/tr35/)." - }, - "largestChangeId": { - "type": "string", - "description": "The largest change id.", - "format": "int64" - }, - "maxUploadSizes": { - "type": "array", - "description": "List of max upload sizes for each file type. The most specific type takes precedence.", - "items": { - "type": "object", - "properties": { - "size": { - "type": "string", - "description": "The max upload size for this type.", - "format": "int64" - }, - "type": { - "type": "string", - "description": "The file type." - } - } - } - }, - "name": { - "type": "string", - "description": "The name of the current user." - }, - "permissionId": { - "type": "string", - "description": "The current user's ID as visible in the permissions collection." - }, - "quotaBytesByService": { - "type": "array", - "description": "The amount of storage quota used by different Google services.", - "items": { - "type": "object", - "properties": { - "bytesUsed": { - "type": "string", - "description": "The storage quota bytes used by the service.", - "format": "int64" - }, - "serviceName": { - "type": "string", - "description": "The service's name, e.g. DRIVE, GMAIL, or PHOTOS." - } - } - } - }, - "quotaBytesTotal": { - "type": "string", - "description": "The total number of quota bytes.", - "format": "int64" - }, - "quotaBytesUsed": { - "type": "string", - "description": "The number of quota bytes used by Google Drive.", - "format": "int64" - }, - "quotaBytesUsedAggregate": { - "type": "string", - "description": "The number of quota bytes used by all Google apps (Drive, Picasa, etc.).", - "format": "int64" - }, - "quotaBytesUsedInTrash": { - "type": "string", - "description": "The number of quota bytes used by trashed items.", - "format": "int64" - }, - "quotaType": { - "type": "string", - "description": "The type of the user's storage quota. Possible values are: \n- LIMITED \n- UNLIMITED" - }, - "remainingChangeIds": { - "type": "string", - "description": "The number of remaining change ids.", - "format": "int64" - }, - "rootFolderId": { - "type": "string", - "description": "The id of the root folder." - }, - "selfLink": { - "type": "string", - "description": "A link back to this item." - }, - "user": { - "$ref": "User", - "description": "The authenticated user." - } - } - }, - "App": { - "id": "App", - "type": "object", - "description": "The apps resource provides a list of the apps that a user has installed, with information about each app's supported MIME types, file extensions, and other details.", - "properties": { - "authorized": { - "type": "boolean", - "description": "Whether the app is authorized to access data on the user's Drive." - }, - "createInFolderTemplate": { - "type": "string", - "description": "The template url to create a new file with this app in a given folder. The template will contain {folderId} to be replaced by the folder to create the new file in." - }, - "createUrl": { - "type": "string", - "description": "The url to create a new file with this app." - }, - "hasDriveWideScope": { - "type": "boolean", - "description": "Whether the app has drive-wide scope. An app with drive-wide scope can access all files in the user's drive." - }, - "icons": { - "type": "array", - "description": "The various icons for the app.", - "items": { - "type": "object", - "properties": { - "category": { - "type": "string", - "description": "Category of the icon. Allowed values are: \n- application - icon for the application \n- document - icon for a file associated with the app \n- documentShared - icon for a shared file associated with the app" - }, - "iconUrl": { - "type": "string", - "description": "URL for the icon." - }, - "size": { - "type": "integer", - "description": "Size of the icon. Represented as the maximum of the width and height.", - "format": "int32" - } - } - } - }, - "id": { - "type": "string", - "description": "The ID of the app." - }, - "installed": { - "type": "boolean", - "description": "Whether the app is installed." - }, - "kind": { - "type": "string", - "description": "This is always drive#app.", - "default": "drive#app" - }, - "longDescription": { - "type": "string", - "description": "A long description of the app." - }, - "name": { - "type": "string", - "description": "The name of the app." - }, - "objectType": { - "type": "string", - "description": "The type of object this app creates (e.g. Chart). If empty, the app name should be used instead." - }, - "openUrlTemplate": { - "type": "string", - "description": "The template url for opening files with this app. The template will contain {ids} and/or {exportIds} to be replaced by the actual file ids." - }, - "primaryFileExtensions": { - "type": "array", - "description": "The list of primary file extensions.", - "items": { - "type": "string" - } - }, - "primaryMimeTypes": { - "type": "array", - "description": "The list of primary mime types.", - "items": { - "type": "string" - } - }, - "productId": { - "type": "string", - "description": "The ID of the product listing for this app." - }, - "productUrl": { - "type": "string", - "description": "A link to the product listing for this app." - }, - "secondaryFileExtensions": { - "type": "array", - "description": "The list of secondary file extensions.", - "items": { - "type": "string" - } - }, - "secondaryMimeTypes": { - "type": "array", - "description": "The list of secondary mime types.", - "items": { - "type": "string" - } - }, - "shortDescription": { - "type": "string", - "description": "A short description of the app." - }, - "supportsCreate": { - "type": "boolean", - "description": "Whether this app supports creating new objects." - }, - "supportsImport": { - "type": "boolean", - "description": "Whether this app supports importing Google Docs." - }, - "supportsMultiOpen": { - "type": "boolean", - "description": "Whether this app supports opening more than one file." - }, - "supportsOfflineCreate": { - "type": "boolean", - "description": "Whether this app supports creating new files when offline." - }, - "useByDefault": { - "type": "boolean", - "description": "Whether the app is selected as the default handler for the types it supports." - } - } - }, - "AppList": { - "id": "AppList", - "type": "object", - "description": "A list of third-party applications which the user has installed or given access to Google Drive.", - "properties": { - "defaultAppIds": { - "type": "array", - "description": "List of app IDs that the user has specified to use by default. The list is in reverse-priority order (lowest to highest).", - "items": { - "type": "string" - } - }, - "etag": { - "type": "string", - "description": "The ETag of the list." - }, - "items": { - "type": "array", - "description": "The actual list of apps.", - "items": { - "$ref": "App" - } - }, - "kind": { - "type": "string", - "description": "This is always drive#appList.", - "default": "drive#appList" - }, - "selfLink": { - "type": "string", - "description": "A link back to this list." - } - } - }, - "Change": { - "id": "Change", - "type": "object", - "description": "Representation of a change to a file.", - "properties": { - "deleted": { - "type": "boolean", - "description": "Whether the file has been deleted." - }, - "file": { - "$ref": "File", - "description": "The updated state of the file. Present if the file has not been deleted." - }, - "fileId": { - "type": "string", - "description": "The ID of the file associated with this change." - }, - "id": { - "type": "string", - "description": "The ID of the change.", - "format": "int64" - }, - "kind": { - "type": "string", - "description": "This is always drive#change.", - "default": "drive#change" - }, - "modificationDate": { - "type": "string", - "description": "The time of this modification.", - "format": "date-time" - }, - "selfLink": { - "type": "string", - "description": "A link back to this change." - } - } - }, - "ChangeList": { - "id": "ChangeList", - "type": "object", - "description": "A list of changes for a user.", - "properties": { - "etag": { - "type": "string", - "description": "The ETag of the list." - }, - "items": { - "type": "array", - "description": "The actual list of changes.", - "items": { - "$ref": "Change" - } - }, - "kind": { - "type": "string", - "description": "This is always drive#changeList.", - "default": "drive#changeList" - }, - "largestChangeId": { - "type": "string", - "description": "The current largest change ID.", - "format": "int64" - }, - "nextLink": { - "type": "string", - "description": "A link to the next page of changes." - }, - "nextPageToken": { - "type": "string", - "description": "The page token for the next page of changes." - }, - "selfLink": { - "type": "string", - "description": "A link back to this list." - } - } - }, - "Channel": { - "id": "Channel", - "type": "object", - "description": "An notification channel used to watch for resource changes.", - "properties": { - "address": { - "type": "string", - "description": "The address where notifications are delivered for this channel." - }, - "expiration": { - "type": "string", - "description": "Date and time of notification channel expiration, expressed as a Unix timestamp, in milliseconds. Optional.", - "format": "int64" - }, - "id": { - "type": "string", - "description": "A UUID or similar unique string that identifies this channel." - }, - "kind": { - "type": "string", - "description": "Identifies this as a notification channel used to watch for changes to a resource. Value: the fixed string \"api#channel\".", - "default": "api#channel" - }, - "params": { - "type": "object", - "description": "Additional parameters controlling delivery channel behavior. Optional.", - "additionalProperties": { - "type": "string", - "description": "Declares a new parameter by name." - } - }, - "payload": { - "type": "boolean", - "description": "A Boolean value to indicate whether payload is wanted. Optional." - }, - "resourceId": { - "type": "string", - "description": "An opaque ID that identifies the resource being watched on this channel. Stable across different API versions." - }, - "resourceUri": { - "type": "string", - "description": "A version-specific identifier for the watched resource." - }, - "token": { - "type": "string", - "description": "An arbitrary string delivered to the target address with each notification delivered over this channel. Optional." - }, - "type": { - "type": "string", - "description": "The type of delivery mechanism used for this channel." - } - } - }, - "ChildList": { - "id": "ChildList", - "type": "object", - "description": "A list of children of a file.", - "properties": { - "etag": { - "type": "string", - "description": "The ETag of the list." - }, - "items": { - "type": "array", - "description": "The actual list of children.", - "items": { - "$ref": "ChildReference" - } - }, - "kind": { - "type": "string", - "description": "This is always drive#childList.", - "default": "drive#childList" - }, - "nextLink": { - "type": "string", - "description": "A link to the next page of children." - }, - "nextPageToken": { - "type": "string", - "description": "The page token for the next page of children." - }, - "selfLink": { - "type": "string", - "description": "A link back to this list." - } - } - }, - "ChildReference": { - "id": "ChildReference", - "type": "object", - "description": "A reference to a folder's child.", - "properties": { - "childLink": { - "type": "string", - "description": "A link to the child." - }, - "id": { - "type": "string", - "description": "The ID of the child.", - "annotations": { - "required": [ - "drive.children.insert" - ] - } - }, - "kind": { - "type": "string", - "description": "This is always drive#childReference.", - "default": "drive#childReference" - }, - "selfLink": { - "type": "string", - "description": "A link back to this reference." - } - } - }, - "Comment": { - "id": "Comment", - "type": "object", - "description": "A JSON representation of a comment on a file in Google Drive.", - "properties": { - "anchor": { - "type": "string", - "description": "A region of the document represented as a JSON string. See anchor documentation for details on how to define and interpret anchor properties." - }, - "author": { - "$ref": "User", - "description": "The user who wrote this comment." - }, - "commentId": { - "type": "string", - "description": "The ID of the comment." - }, - "content": { - "type": "string", - "description": "The plain text content used to create this comment. This is not HTML safe and should only be used as a starting point to make edits to a comment's content.", - "annotations": { - "required": [ - "drive.comments.insert", - "drive.comments.update" - ] - } - }, - "context": { - "type": "object", - "description": "The context of the file which is being commented on.", - "properties": { - "type": { - "type": "string", - "description": "The MIME type of the context snippet." - }, - "value": { - "type": "string", - "description": "Data representation of the segment of the file being commented on. In the case of a text file for example, this would be the actual text that the comment is about." - } - } - }, - "createdDate": { - "type": "string", - "description": "The date when this comment was first created.", - "format": "date-time" - }, - "deleted": { - "type": "boolean", - "description": "Whether this comment has been deleted. If a comment has been deleted the content will be cleared and this will only represent a comment that once existed." - }, - "fileId": { - "type": "string", - "description": "The file which this comment is addressing." - }, - "fileTitle": { - "type": "string", - "description": "The title of the file which this comment is addressing." - }, - "htmlContent": { - "type": "string", - "description": "HTML formatted content for this comment." - }, - "kind": { - "type": "string", - "description": "This is always drive#comment.", - "default": "drive#comment" - }, - "modifiedDate": { - "type": "string", - "description": "The date when this comment or any of its replies were last modified.", - "format": "date-time" - }, - "replies": { - "type": "array", - "description": "Replies to this post.", - "items": { - "$ref": "CommentReply" - } - }, - "selfLink": { - "type": "string", - "description": "A link back to this comment." - }, - "status": { - "type": "string", - "description": "The status of this comment. Status can be changed by posting a reply to a comment with the desired status. \n- \"open\" - The comment is still open. \n- \"resolved\" - The comment has been resolved by one of its replies." - } - } - }, - "CommentList": { - "id": "CommentList", - "type": "object", - "description": "A JSON representation of a list of comments on a file in Google Drive.", - "properties": { - "items": { - "type": "array", - "description": "List of comments.", - "items": { - "$ref": "Comment" - } - }, - "kind": { - "type": "string", - "description": "This is always drive#commentList.", - "default": "drive#commentList" - }, - "nextLink": { - "type": "string", - "description": "A link to the next page of comments." - }, - "nextPageToken": { - "type": "string", - "description": "The token to use to request the next page of results." - }, - "selfLink": { - "type": "string", - "description": "A link back to this list." - } - } - }, - "CommentReply": { - "id": "CommentReply", - "type": "object", - "description": "A JSON representation of a reply to a comment on a file in Google Drive.", - "properties": { - "author": { - "$ref": "User", - "description": "The user who wrote this reply." - }, - "content": { - "type": "string", - "description": "The plain text content used to create this reply. This is not HTML safe and should only be used as a starting point to make edits to a reply's content. This field is required on inserts if no verb is specified (resolve/reopen).", - "annotations": { - "required": [ - "drive.replies.update" - ] - } - }, - "createdDate": { - "type": "string", - "description": "The date when this reply was first created.", - "format": "date-time" - }, - "deleted": { - "type": "boolean", - "description": "Whether this reply has been deleted. If a reply has been deleted the content will be cleared and this will only represent a reply that once existed." - }, - "htmlContent": { - "type": "string", - "description": "HTML formatted content for this reply." - }, - "kind": { - "type": "string", - "description": "This is always drive#commentReply.", - "default": "drive#commentReply" - }, - "modifiedDate": { - "type": "string", - "description": "The date when this reply was last modified.", - "format": "date-time" - }, - "replyId": { - "type": "string", - "description": "The ID of the reply." - }, - "verb": { - "type": "string", - "description": "The action this reply performed to the parent comment. When creating a new reply this is the action to be perform to the parent comment. Possible values are: \n- \"resolve\" - To resolve a comment. \n- \"reopen\" - To reopen (un-resolve) a comment." - } - } - }, - "CommentReplyList": { - "id": "CommentReplyList", - "type": "object", - "description": "A JSON representation of a list of replies to a comment on a file in Google Drive.", - "properties": { - "items": { - "type": "array", - "description": "List of reply.", - "items": { - "$ref": "CommentReply" - } - }, - "kind": { - "type": "string", - "description": "This is always drive#commentReplyList.", - "default": "drive#commentReplyList" - }, - "nextLink": { - "type": "string", - "description": "A link to the next page of replies." - }, - "nextPageToken": { - "type": "string", - "description": "The token to use to request the next page of results." - }, - "selfLink": { - "type": "string", - "description": "A link back to this list." - } - } - }, - "File": { - "id": "File", - "type": "object", - "description": "The metadata for a file.", - "properties": { - "alternateLink": { - "type": "string", - "description": "A link for opening the file in a relevant Google editor or viewer." - }, - "appDataContents": { - "type": "boolean", - "description": "Whether this file is in the Application Data folder." - }, - "copyable": { - "type": "boolean", - "description": "Whether the file can be copied by the current user." - }, - "createdDate": { - "type": "string", - "description": "Create time for this file (formatted RFC 3339 timestamp).", - "format": "date-time" - }, - "defaultOpenWithLink": { - "type": "string", - "description": "A link to open this file with the user's default app for this file. Only populated when the drive.apps.readonly scope is used." - }, - "description": { - "type": "string", - "description": "A short description of the file." - }, - "downloadUrl": { - "type": "string", - "description": "Short lived download URL for the file. This is only populated for files with content stored in Drive." - }, - "editable": { - "type": "boolean", - "description": "Whether the file can be edited by the current user." - }, - "embedLink": { - "type": "string", - "description": "A link for embedding the file." - }, - "etag": { - "type": "string", - "description": "ETag of the file." - }, - "explicitlyTrashed": { - "type": "boolean", - "description": "Whether this file has been explicitly trashed, as opposed to recursively trashed. This will only be populated if the file is trashed." - }, - "exportLinks": { - "type": "object", - "description": "Links for exporting Google Docs to specific formats.", - "additionalProperties": { - "type": "string", - "description": "A mapping from export format to URL" - } - }, - "fileExtension": { - "type": "string", - "description": "The file extension used when downloading this file. This field is read only. To set the extension, include it in the title when creating the file. This is only populated for files with content stored in Drive." - }, - "fileSize": { - "type": "string", - "description": "The size of the file in bytes. This is only populated for files with content stored in Drive.", - "format": "int64" - }, - "headRevisionId": { - "type": "string", - "description": "The ID of the file's head revision. This will only be populated for files with content stored in Drive." - }, - "iconLink": { - "type": "string", - "description": "A link to the file's icon." - }, - "id": { - "type": "string", - "description": "The ID of the file." - }, - "imageMediaMetadata": { - "type": "object", - "description": "Metadata about image media. This will only be present for image types, and its contents will depend on what can be parsed from the image content.", - "properties": { - "aperture": { - "type": "number", - "description": "The aperture used to create the photo (f-number).", - "format": "float" - }, - "cameraMake": { - "type": "string", - "description": "The make of the camera used to create the photo." - }, - "cameraModel": { - "type": "string", - "description": "The model of the camera used to create the photo." - }, - "colorSpace": { - "type": "string", - "description": "The color space of the photo." - }, - "date": { - "type": "string", - "description": "The date and time the photo was taken (EXIF format timestamp)." - }, - "exposureBias": { - "type": "number", - "description": "The exposure bias of the photo (APEX value).", - "format": "float" - }, - "exposureMode": { - "type": "string", - "description": "The exposure mode used to create the photo." - }, - "exposureTime": { - "type": "number", - "description": "The length of the exposure, in seconds.", - "format": "float" - }, - "flashUsed": { - "type": "boolean", - "description": "Whether a flash was used to create the photo." - }, - "focalLength": { - "type": "number", - "description": "The focal length used to create the photo, in millimeters.", - "format": "float" - }, - "height": { - "type": "integer", - "description": "The height of the image in pixels.", - "format": "int32" - }, - "isoSpeed": { - "type": "integer", - "description": "The ISO speed used to create the photo.", - "format": "int32" - }, - "lens": { - "type": "string", - "description": "The lens used to create the photo." - }, - "location": { - "type": "object", - "description": "Geographic location information stored in the image.", - "properties": { - "altitude": { - "type": "number", - "description": "The altitude stored in the image.", - "format": "double" - }, - "latitude": { - "type": "number", - "description": "The latitude stored in the image.", - "format": "double" - }, - "longitude": { - "type": "number", - "description": "The longitude stored in the image.", - "format": "double" - } - } - }, - "maxApertureValue": { - "type": "number", - "description": "The smallest f-number of the lens at the focal length used to create the photo (APEX value).", - "format": "float" - }, - "meteringMode": { - "type": "string", - "description": "The metering mode used to create the photo." - }, - "rotation": { - "type": "integer", - "description": "The rotation in clockwise degrees from the image's original orientation.", - "format": "int32" - }, - "sensor": { - "type": "string", - "description": "The type of sensor used to create the photo." - }, - "subjectDistance": { - "type": "integer", - "description": "The distance to the subject of the photo, in meters.", - "format": "int32" - }, - "whiteBalance": { - "type": "string", - "description": "The white balance mode used to create the photo." - }, - "width": { - "type": "integer", - "description": "The width of the image in pixels.", - "format": "int32" - } - } - }, - "indexableText": { - "type": "object", - "description": "Indexable text attributes for the file (can only be written)", - "properties": { - "text": { - "type": "string", - "description": "The text to be indexed for this file." - } - } - }, - "kind": { - "type": "string", - "description": "The type of file. This is always drive#file.", - "default": "drive#file" - }, - "labels": { - "type": "object", - "description": "A group of labels for the file.", - "properties": { - "hidden": { - "type": "boolean", - "description": "Deprecated." - }, - "restricted": { - "type": "boolean", - "description": "Whether viewers are prevented from downloading this file." - }, - "starred": { - "type": "boolean", - "description": "Whether this file is starred by the user." - }, - "trashed": { - "type": "boolean", - "description": "Whether this file has been trashed." - }, - "viewed": { - "type": "boolean", - "description": "Whether this file has been viewed by this user." - } - } - }, - "lastModifyingUser": { - "$ref": "User", - "description": "The last user to modify this file." - }, - "lastModifyingUserName": { - "type": "string", - "description": "Name of the last user to modify this file." - }, - "lastViewedByMeDate": { - "type": "string", - "description": "Last time this file was viewed by the user (formatted RFC 3339 timestamp).", - "format": "date-time" - }, - "markedViewedByMeDate": { - "type": "string", - "description": "Time this file was explicitly marked viewed by the user (formatted RFC 3339 timestamp).", - "format": "date-time" - }, - "md5Checksum": { - "type": "string", - "description": "An MD5 checksum for the content of this file. This is populated only for files with content stored in Drive." - }, - "mimeType": { - "type": "string", - "description": "The MIME type of the file. This is only mutable on update when uploading new content. This field can be left blank, and the mimetype will be determined from the uploaded content's MIME type." - }, - "modifiedByMeDate": { - "type": "string", - "description": "Last time this file was modified by the user (formatted RFC 3339 timestamp). Note that setting modifiedDate will also update the modifiedByMe date for the user which set the date.", - "format": "date-time" - }, - "modifiedDate": { - "type": "string", - "description": "Last time this file was modified by anyone (formatted RFC 3339 timestamp). This is only mutable on update when the setModifiedDate parameter is set.", - "format": "date-time" - }, - "openWithLinks": { - "type": "object", - "description": "A map of the id of each of the user's apps to a link to open this file with that app. Only populated when the drive.apps.readonly scope is used.", - "additionalProperties": { - "type": "string" - } - }, - "originalFilename": { - "type": "string", - "description": "The original filename if the file was uploaded manually, or the original title if the file was inserted through the API. Note that renames of the title will not change the original filename. This will only be populated on files with content stored in Drive." - }, - "ownerNames": { - "type": "array", - "description": "Name(s) of the owner(s) of this file.", - "items": { - "type": "string" - } - }, - "owners": { - "type": "array", - "description": "The owner(s) of this file.", - "items": { - "$ref": "User" - } - }, - "parents": { - "type": "array", - "description": "Collection of parent folders which contain this file.\nSetting this field will put the file in all of the provided folders. On insert, if no folders are provided, the file will be placed in the default root folder.", - "items": { - "$ref": "ParentReference" - } - }, - "permissions": { - "type": "array", - "description": "The list of permissions for users with access to this file.", - "items": { - "$ref": "Permission" - } - }, - "properties": { - "type": "array", - "description": "The list of properties.", - "items": { - "$ref": "Property" - } - }, - "quotaBytesUsed": { - "type": "string", - "description": "The number of quota bytes used by this file.", - "format": "int64" - }, - "selfLink": { - "type": "string", - "description": "A link back to this file." - }, - "shared": { - "type": "boolean", - "description": "Whether the file has been shared." - }, - "sharedWithMeDate": { - "type": "string", - "description": "Time at which this file was shared with the user (formatted RFC 3339 timestamp).", - "format": "date-time" - }, - "sharingUser": { - "$ref": "User", - "description": "User that shared the item with the current user, if available." - }, - "thumbnail": { - "type": "object", - "description": "Thumbnail for the file. Only accepted on upload and for files that are not already thumbnailed by Google.", - "properties": { - "image": { - "type": "string", - "description": "The URL-safe Base64 encoded bytes of the thumbnail image.", - "format": "byte" - }, - "mimeType": { - "type": "string", - "description": "The MIME type of the thumbnail." - } - } - }, - "thumbnailLink": { - "type": "string", - "description": "A link to the file's thumbnail." - }, - "title": { - "type": "string", - "description": "The title of this file." - }, - "userPermission": { - "$ref": "Permission", - "description": "The permissions for the authenticated user on this file." - }, - "version": { - "type": "string", - "description": "A monotonically increasing version number for the file. This reflects every change made to the file on the server, even those not visible to the requesting user.", - "format": "int64" - }, - "videoMediaMetadata": { - "type": "object", - "description": "Metadata about video media. This will only be present for video types.", - "properties": { - "durationMillis": { - "type": "string", - "description": "The duration of the video in milliseconds.", - "format": "int64" - }, - "height": { - "type": "integer", - "description": "The height of the video in pixels.", - "format": "int32" - }, - "width": { - "type": "integer", - "description": "The width of the video in pixels.", - "format": "int32" - } - } - }, - "webContentLink": { - "type": "string", - "description": "A link for downloading the content of the file in a browser using cookie based authentication. In cases where the content is shared publicly, the content can be downloaded without any credentials." - }, - "webViewLink": { - "type": "string", - "description": "A link only available on public folders for viewing their static web assets (HTML, CSS, JS, etc) via Google Drive's Website Hosting." - }, - "writersCanShare": { - "type": "boolean", - "description": "Whether writers can share the document with other users." - } - } - }, - "FileList": { - "id": "FileList", - "type": "object", - "description": "A list of files.", - "properties": { - "etag": { - "type": "string", - "description": "The ETag of the list." - }, - "items": { - "type": "array", - "description": "The actual list of files.", - "items": { - "$ref": "File" - } - }, - "kind": { - "type": "string", - "description": "This is always drive#fileList.", - "default": "drive#fileList" - }, - "nextLink": { - "type": "string", - "description": "A link to the next page of files." - }, - "nextPageToken": { - "type": "string", - "description": "The page token for the next page of files." - }, - "selfLink": { - "type": "string", - "description": "A link back to this list." - } - } - }, - "ParentList": { - "id": "ParentList", - "type": "object", - "description": "A list of a file's parents.", - "properties": { - "etag": { - "type": "string", - "description": "The ETag of the list." - }, - "items": { - "type": "array", - "description": "The actual list of parents.", - "items": { - "$ref": "ParentReference" - } - }, - "kind": { - "type": "string", - "description": "This is always drive#parentList.", - "default": "drive#parentList" - }, - "selfLink": { - "type": "string", - "description": "A link back to this list." - } - } - }, - "ParentReference": { - "id": "ParentReference", - "type": "object", - "description": "A reference to a file's parent.", - "properties": { - "id": { - "type": "string", - "description": "The ID of the parent.", - "annotations": { - "required": [ - "drive.parents.insert" - ] - } - }, - "isRoot": { - "type": "boolean", - "description": "Whether or not the parent is the root folder." - }, - "kind": { - "type": "string", - "description": "This is always drive#parentReference.", - "default": "drive#parentReference" - }, - "parentLink": { - "type": "string", - "description": "A link to the parent." - }, - "selfLink": { - "type": "string", - "description": "A link back to this reference." - } - } - }, - "Permission": { - "id": "Permission", - "type": "object", - "description": "A permission for a file.", - "properties": { - "additionalRoles": { - "type": "array", - "description": "Additional roles for this user. Only commenter is currently allowed.", - "items": { - "type": "string" - } - }, - "authKey": { - "type": "string", - "description": "The authkey parameter required for this permission." - }, - "domain": { - "type": "string", - "description": "The domain name of the entity this permission refers to. This is an output-only field which is present when the permission type is user, group or domain." - }, - "emailAddress": { - "type": "string", - "description": "The email address of the user this permission refers to. This is an output-only field which is present when the permission type is user." - }, - "etag": { - "type": "string", - "description": "The ETag of the permission." - }, - "id": { - "type": "string", - "description": "The ID of the user this permission refers to, and identical to the permissionId in the About and Files resources. When making a drive.permissions.insert request, exactly one of the id or value fields must be specified." - }, - "kind": { - "type": "string", - "description": "This is always drive#permission.", - "default": "drive#permission" - }, - "name": { - "type": "string", - "description": "The name for this permission." - }, - "photoLink": { - "type": "string", - "description": "A link to the profile photo, if available." - }, - "role": { - "type": "string", - "description": "The primary role for this user. Allowed values are: \n- owner \n- reader \n- writer", - "annotations": { - "required": [ - "drive.permissions.insert" - ] - } - }, - "selfLink": { - "type": "string", - "description": "A link back to this permission." - }, - "type": { - "type": "string", - "description": "The account type. Allowed values are: \n- user \n- group \n- domain \n- anyone", - "annotations": { - "required": [ - "drive.permissions.insert" - ] - } - }, - "value": { - "type": "string", - "description": "The email address or domain name for the entity. This is used during inserts and is not populated in responses. When making a drive.permissions.insert request, exactly one of the id or value fields must be specified." - }, - "withLink": { - "type": "boolean", - "description": "Whether the link is required for this permission." - } - } - }, - "PermissionId": { - "id": "PermissionId", - "type": "object", - "description": "An ID for a user or group as seen in Permission items.", - "properties": { - "id": { - "type": "string", - "description": "The permission ID." - }, - "kind": { - "type": "string", - "description": "This is always drive#permissionId.", - "default": "drive#permissionId" - } - } - }, - "PermissionList": { - "id": "PermissionList", - "type": "object", - "description": "A list of permissions associated with a file.", - "properties": { - "etag": { - "type": "string", - "description": "The ETag of the list." - }, - "items": { - "type": "array", - "description": "The actual list of permissions.", - "items": { - "$ref": "Permission" - } - }, - "kind": { - "type": "string", - "description": "This is always drive#permissionList.", - "default": "drive#permissionList" - }, - "selfLink": { - "type": "string", - "description": "A link back to this list." - } - } - }, - "Property": { - "id": "Property", - "type": "object", - "description": "A key-value pair that is either public or private to an application.", - "properties": { - "etag": { - "type": "string", - "description": "ETag of the property." - }, - "key": { - "type": "string", - "description": "The key of this property." - }, - "kind": { - "type": "string", - "description": "This is always drive#property.", - "default": "drive#property" - }, - "selfLink": { - "type": "string", - "description": "The link back to this property." - }, - "value": { - "type": "string", - "description": "The value of this property." - }, - "visibility": { - "type": "string", - "description": "The visibility of this property." - } - } - }, - "PropertyList": { - "id": "PropertyList", - "type": "object", - "description": "A collection of properties, key-value pairs that are either public or private to an application.", - "properties": { - "etag": { - "type": "string", - "description": "The ETag of the list." - }, - "items": { - "type": "array", - "description": "The list of properties.", - "items": { - "$ref": "Property" - } - }, - "kind": { - "type": "string", - "description": "This is always drive#propertyList.", - "default": "drive#propertyList" - }, - "selfLink": { - "type": "string", - "description": "The link back to this list." - } - } - }, - "Revision": { - "id": "Revision", - "type": "object", - "description": "A revision of a file.", - "properties": { - "downloadUrl": { - "type": "string", - "description": "Short term download URL for the file. This will only be populated on files with content stored in Drive." - }, - "etag": { - "type": "string", - "description": "The ETag of the revision." - }, - "exportLinks": { - "type": "object", - "description": "Links for exporting Google Docs to specific formats.", - "additionalProperties": { - "type": "string", - "description": "A mapping from export format to URL" - } - }, - "fileSize": { - "type": "string", - "description": "The size of the revision in bytes. This will only be populated on files with content stored in Drive.", - "format": "int64" - }, - "id": { - "type": "string", - "description": "The ID of the revision." - }, - "kind": { - "type": "string", - "description": "This is always drive#revision.", - "default": "drive#revision" - }, - "lastModifyingUser": { - "$ref": "User", - "description": "The last user to modify this revision." - }, - "lastModifyingUserName": { - "type": "string", - "description": "Name of the last user to modify this revision." - }, - "md5Checksum": { - "type": "string", - "description": "An MD5 checksum for the content of this revision. This will only be populated on files with content stored in Drive." - }, - "mimeType": { - "type": "string", - "description": "The MIME type of the revision." - }, - "modifiedDate": { - "type": "string", - "description": "Last time this revision was modified (formatted RFC 3339 timestamp).", - "format": "date-time" - }, - "originalFilename": { - "type": "string", - "description": "The original filename when this revision was created. This will only be populated on files with content stored in Drive." - }, - "pinned": { - "type": "boolean", - "description": "Whether this revision is pinned to prevent automatic purging. This will only be populated and can only be modified on files with content stored in Drive which are not Google Docs. Revisions can also be pinned when they are created through the drive.files.insert/update/copy by using the pinned query parameter." - }, - "publishAuto": { - "type": "boolean", - "description": "Whether subsequent revisions will be automatically republished. This is only populated and can only be modified for Google Docs." - }, - "published": { - "type": "boolean", - "description": "Whether this revision is published. This is only populated and can only be modified for Google Docs." - }, - "publishedLink": { - "type": "string", - "description": "A link to the published revision." - }, - "publishedOutsideDomain": { - "type": "boolean", - "description": "Whether this revision is published outside the domain. This is only populated and can only be modified for Google Docs." - }, - "selfLink": { - "type": "string", - "description": "A link back to this revision." - } - } - }, - "RevisionList": { - "id": "RevisionList", - "type": "object", - "description": "A list of revisions of a file.", - "properties": { - "etag": { - "type": "string", - "description": "The ETag of the list." - }, - "items": { - "type": "array", - "description": "The actual list of revisions.", - "items": { - "$ref": "Revision" - } - }, - "kind": { - "type": "string", - "description": "This is always drive#revisionList.", - "default": "drive#revisionList" - }, - "selfLink": { - "type": "string", - "description": "A link back to this list." - } - } - }, - "User": { - "id": "User", - "type": "object", - "description": "The JSON template for a user.", - "properties": { - "displayName": { - "type": "string", - "description": "A plain text displayable name for this user." - }, - "emailAddress": { - "type": "string", - "description": "The email address of the user." - }, - "isAuthenticatedUser": { - "type": "boolean", - "description": "Whether this user is the same as the authenticated user for whom the request was made." - }, - "kind": { - "type": "string", - "description": "This is always drive#user.", - "default": "drive#user" - }, - "permissionId": { - "type": "string", - "description": "The user's ID as visible in the permissions collection." - }, - "picture": { - "type": "object", - "description": "The user's profile picture.", - "properties": { - "url": { - "type": "string", - "description": "A URL that points to a profile picture of this user." - } - } - } - } - } - }, - "resources": { - "about": { - "methods": { - "get": { - "id": "drive.about.get", - "path": "about", - "httpMethod": "GET", - "description": "Gets the information about the current user along with Drive API settings", - "parameters": { - "includeSubscribed": { - "type": "boolean", - "description": "When calculating the number of remaining change IDs, whether to include public files the user has opened and shared files. When set to false, this counts only change IDs for owned files and any shared or public files that the user has explicitly added to a folder they own.", - "default": "true", - "location": "query" - }, - "maxChangeIdCount": { - "type": "string", - "description": "Maximum number of remaining change IDs to count", - "default": "1", - "format": "int64", - "location": "query" - }, - "startChangeId": { - "type": "string", - "description": "Change ID to start counting from when calculating number of remaining change IDs", - "format": "int64", - "location": "query" - } - }, - "response": { - "$ref": "About" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - } - } - }, - "apps": { - "methods": { - "get": { - "id": "drive.apps.get", - "path": "apps/{appId}", - "httpMethod": "GET", - "description": "Gets a specific app.", - "parameters": { - "appId": { - "type": "string", - "description": "The ID of the app.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "appId" - ], - "response": { - "$ref": "App" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "list": { - "id": "drive.apps.list", - "path": "apps", - "httpMethod": "GET", - "description": "Lists a user's installed apps.", - "parameters": { - "appFilterExtensions": { - "type": "string", - "description": "A comma-separated list of file extensions for open with filtering. All apps within the given app query scope which can open any of the given file extensions will be included in the response. If appFilterMimeTypes are provided as well, the result is a union of the two resulting app lists.", - "default": "", - "location": "query" - }, - "appFilterMimeTypes": { - "type": "string", - "description": "A comma-separated list of MIME types for open with filtering. All apps within the given app query scope which can open any of the given MIME types will be included in the response. If appFilterExtensions are provided as well, the result is a union of the two resulting app lists.", - "default": "", - "location": "query" - }, - "languageCode": { - "type": "string", - "description": "A language or locale code, as defined by BCP 47, with some extensions from Unicode's LDML format (http://www.unicode.org/reports/tr35/).", - "location": "query" - } - }, - "response": { - "$ref": "AppList" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive.apps.readonly" - ] - } - } - }, - "changes": { - "methods": { - "get": { - "id": "drive.changes.get", - "path": "changes/{changeId}", - "httpMethod": "GET", - "description": "Gets a specific change.", - "parameters": { - "changeId": { - "type": "string", - "description": "The ID of the change.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "changeId" - ], - "response": { - "$ref": "Change" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "list": { - "id": "drive.changes.list", - "path": "changes", - "httpMethod": "GET", - "description": "Lists the changes for a user.", - "parameters": { - "includeDeleted": { - "type": "boolean", - "description": "Whether to include deleted items.", - "default": "true", - "location": "query" - }, - "includeSubscribed": { - "type": "boolean", - "description": "Whether to include public files the user has opened and shared files. When set to false, the list only includes owned files plus any shared or public files the user has explicitly added to a folder they own.", - "default": "true", - "location": "query" - }, - "maxResults": { - "type": "integer", - "description": "Maximum number of changes to return.", - "default": "100", - "format": "int32", - "minimum": "1", - "location": "query" - }, - "pageToken": { - "type": "string", - "description": "Page token for changes.", - "location": "query" - }, - "startChangeId": { - "type": "string", - "description": "Change ID to start listing changes from.", - "format": "int64", - "location": "query" - } - }, - "response": { - "$ref": "ChangeList" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ], - "supportsSubscription": true - }, - "watch": { - "id": "drive.changes.watch", - "path": "changes/watch", - "httpMethod": "POST", - "description": "Subscribe to changes for a user.", - "parameters": { - "includeDeleted": { - "type": "boolean", - "description": "Whether to include deleted items.", - "default": "true", - "location": "query" - }, - "includeSubscribed": { - "type": "boolean", - "description": "Whether to include public files the user has opened and shared files. When set to false, the list only includes owned files plus any shared or public files the user has explicitly added to a folder they own.", - "default": "true", - "location": "query" - }, - "maxResults": { - "type": "integer", - "description": "Maximum number of changes to return.", - "default": "100", - "format": "int32", - "minimum": "1", - "location": "query" - }, - "pageToken": { - "type": "string", - "description": "Page token for changes.", - "location": "query" - }, - "startChangeId": { - "type": "string", - "description": "Change ID to start listing changes from.", - "format": "int64", - "location": "query" - } - }, - "request": { - "$ref": "Channel", - "parameterName": "resource" - }, - "response": { - "$ref": "Channel" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ], - "supportsSubscription": true - } - } - }, - "channels": { - "methods": { - "stop": { - "id": "drive.channels.stop", - "path": "channels/stop", - "httpMethod": "POST", - "description": "Stop watching resources through this channel", - "request": { - "$ref": "Channel", - "parameterName": "resource" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - } - } - }, - "children": { - "methods": { - "delete": { - "id": "drive.children.delete", - "path": "files/{folderId}/children/{childId}", - "httpMethod": "DELETE", - "description": "Removes a child from a folder.", - "parameters": { - "childId": { - "type": "string", - "description": "The ID of the child.", - "required": true, - "location": "path" - }, - "folderId": { - "type": "string", - "description": "The ID of the folder.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "folderId", - "childId" - ], - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "get": { - "id": "drive.children.get", - "path": "files/{folderId}/children/{childId}", - "httpMethod": "GET", - "description": "Gets a specific child reference.", - "parameters": { - "childId": { - "type": "string", - "description": "The ID of the child.", - "required": true, - "location": "path" - }, - "folderId": { - "type": "string", - "description": "The ID of the folder.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "folderId", - "childId" - ], - "response": { - "$ref": "ChildReference" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "insert": { - "id": "drive.children.insert", - "path": "files/{folderId}/children", - "httpMethod": "POST", - "description": "Inserts a file into a folder.", - "parameters": { - "folderId": { - "type": "string", - "description": "The ID of the folder.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "folderId" - ], - "request": { - "$ref": "ChildReference" - }, - "response": { - "$ref": "ChildReference" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "list": { - "id": "drive.children.list", - "path": "files/{folderId}/children", - "httpMethod": "GET", - "description": "Lists a folder's children.", - "parameters": { - "folderId": { - "type": "string", - "description": "The ID of the folder.", - "required": true, - "location": "path" - }, - "maxResults": { - "type": "integer", - "description": "Maximum number of children to return.", - "default": "100", - "format": "int32", - "minimum": "0", - "location": "query" - }, - "pageToken": { - "type": "string", - "description": "Page token for children.", - "location": "query" - }, - "q": { - "type": "string", - "description": "Query string for searching children.", - "location": "query" - } - }, - "parameterOrder": [ - "folderId" - ], - "response": { - "$ref": "ChildList" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - } - } - }, - "comments": { - "methods": { - "delete": { - "id": "drive.comments.delete", - "path": "files/{fileId}/comments/{commentId}", - "httpMethod": "DELETE", - "description": "Deletes a comment.", - "parameters": { - "commentId": { - "type": "string", - "description": "The ID of the comment.", - "required": true, - "location": "path" - }, - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "commentId" - ], - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "get": { - "id": "drive.comments.get", - "path": "files/{fileId}/comments/{commentId}", - "httpMethod": "GET", - "description": "Gets a comment by ID.", - "parameters": { - "commentId": { - "type": "string", - "description": "The ID of the comment.", - "required": true, - "location": "path" - }, - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "includeDeleted": { - "type": "boolean", - "description": "If set, this will succeed when retrieving a deleted comment, and will include any deleted replies.", - "default": "false", - "location": "query" - } - }, - "parameterOrder": [ - "fileId", - "commentId" - ], - "response": { - "$ref": "Comment" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "insert": { - "id": "drive.comments.insert", - "path": "files/{fileId}/comments", - "httpMethod": "POST", - "description": "Creates a new comment on the given file.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "request": { - "$ref": "Comment" - }, - "response": { - "$ref": "Comment" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "list": { - "id": "drive.comments.list", - "path": "files/{fileId}/comments", - "httpMethod": "GET", - "description": "Lists a file's comments.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "includeDeleted": { - "type": "boolean", - "description": "If set, all comments and replies, including deleted comments and replies (with content stripped) will be returned.", - "default": "false", - "location": "query" - }, - "maxResults": { - "type": "integer", - "description": "The maximum number of discussions to include in the response, used for paging.", - "default": "20", - "format": "int32", - "minimum": "0", - "maximum": "100", - "location": "query" - }, - "pageToken": { - "type": "string", - "description": "The continuation token, used to page through large result sets. To get the next page of results, set this parameter to the value of \"nextPageToken\" from the previous response.", - "location": "query" - }, - "updatedMin": { - "type": "string", - "description": "Only discussions that were updated after this timestamp will be returned. Formatted as an RFC 3339 timestamp.", - "location": "query" - } - }, - "parameterOrder": [ - "fileId" - ], - "response": { - "$ref": "CommentList" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "patch": { - "id": "drive.comments.patch", - "path": "files/{fileId}/comments/{commentId}", - "httpMethod": "PATCH", - "description": "Updates an existing comment. This method supports patch semantics.", - "parameters": { - "commentId": { - "type": "string", - "description": "The ID of the comment.", - "required": true, - "location": "path" - }, - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "commentId" - ], - "request": { - "$ref": "Comment" - }, - "response": { - "$ref": "Comment" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "update": { - "id": "drive.comments.update", - "path": "files/{fileId}/comments/{commentId}", - "httpMethod": "PUT", - "description": "Updates an existing comment.", - "parameters": { - "commentId": { - "type": "string", - "description": "The ID of the comment.", - "required": true, - "location": "path" - }, - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "commentId" - ], - "request": { - "$ref": "Comment" - }, - "response": { - "$ref": "Comment" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - } - } - }, - "files": { - "methods": { - "copy": { - "id": "drive.files.copy", - "path": "files/{fileId}/copy", - "httpMethod": "POST", - "description": "Creates a copy of the specified file.", - "parameters": { - "convert": { - "type": "boolean", - "description": "Whether to convert this file to the corresponding Google Docs format.", - "default": "false", - "location": "query" - }, - "fileId": { - "type": "string", - "description": "The ID of the file to copy.", - "required": true, - "location": "path" - }, - "ocr": { - "type": "boolean", - "description": "Whether to attempt OCR on .jpg, .png, .gif, or .pdf uploads.", - "default": "false", - "location": "query" - }, - "ocrLanguage": { - "type": "string", - "description": "If ocr is true, hints at the language to use. Valid values are ISO 639-1 codes.", - "location": "query" - }, - "pinned": { - "type": "boolean", - "description": "Whether to pin the head revision of the new copy. A file can have a maximum of 200 pinned revisions.", - "default": "false", - "location": "query" - }, - "timedTextLanguage": { - "type": "string", - "description": "The language of the timed text.", - "location": "query" - }, - "timedTextTrackName": { - "type": "string", - "description": "The timed text track name.", - "location": "query" - }, - "visibility": { - "type": "string", - "description": "The visibility of the new file. This parameter is only relevant when the source is not a native Google Doc and convert=false.", - "default": "DEFAULT", - "enum": [ - "DEFAULT", - "PRIVATE" - ], - "enumDescriptions": [ - "The visibility of the new file is determined by the user's default visibility/sharing policies.", - "The new file will be visible to only the owner." - ], - "location": "query" - } - }, - "parameterOrder": [ - "fileId" - ], - "request": { - "$ref": "File" - }, - "response": { - "$ref": "File" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "delete": { - "id": "drive.files.delete", - "path": "files/{fileId}", - "httpMethod": "DELETE", - "description": "Permanently deletes a file by ID. Skips the trash.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file to delete.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "emptyTrash": { - "id": "drive.files.emptyTrash", - "path": "files/trash", - "httpMethod": "DELETE", - "description": "Permanently deletes all of the user's trashed files.", - "scopes": [ - "https://www.googleapis.com/auth/drive" - ] - }, - "get": { - "id": "drive.files.get", - "path": "files/{fileId}", - "httpMethod": "GET", - "description": "Gets a file's metadata by ID.", - "parameters": { - "acknowledgeAbuse": { - "type": "boolean", - "description": "Whether the user is acknowledging the risk of downloading known malware or other abusive files.", - "default": "false", - "location": "query" - }, - "fileId": { - "type": "string", - "description": "The ID for the file in question.", - "required": true, - "location": "path" - }, - "projection": { - "type": "string", - "description": "This parameter is deprecated and has no function.", - "enum": [ - "BASIC", - "FULL" - ], - "enumDescriptions": [ - "Deprecated", - "Deprecated" - ], - "location": "query" - }, - "updateViewedDate": { - "type": "boolean", - "description": "Whether to update the view date after successfully retrieving the file.", - "default": "false", - "location": "query" - } - }, - "parameterOrder": [ - "fileId" - ], - "response": { - "$ref": "File" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ], - "supportsMediaDownload": true, - "supportsSubscription": true - }, - "insert": { - "id": "drive.files.insert", - "path": "files", - "httpMethod": "POST", - "description": "Insert a new file.", - "parameters": { - "convert": { - "type": "boolean", - "description": "Whether to convert this file to the corresponding Google Docs format.", - "default": "false", - "location": "query" - }, - "ocr": { - "type": "boolean", - "description": "Whether to attempt OCR on .jpg, .png, .gif, or .pdf uploads.", - "default": "false", - "location": "query" - }, - "ocrLanguage": { - "type": "string", - "description": "If ocr is true, hints at the language to use. Valid values are ISO 639-1 codes.", - "location": "query" - }, - "pinned": { - "type": "boolean", - "description": "Whether to pin the head revision of the uploaded file. A file can have a maximum of 200 pinned revisions.", - "default": "false", - "location": "query" - }, - "timedTextLanguage": { - "type": "string", - "description": "The language of the timed text.", - "location": "query" - }, - "timedTextTrackName": { - "type": "string", - "description": "The timed text track name.", - "location": "query" - }, - "useContentAsIndexableText": { - "type": "boolean", - "description": "Whether to use the content as indexable text.", - "default": "false", - "location": "query" - }, - "visibility": { - "type": "string", - "description": "The visibility of the new file. This parameter is only relevant when convert=false.", - "default": "DEFAULT", - "enum": [ - "DEFAULT", - "PRIVATE" - ], - "enumDescriptions": [ - "The visibility of the new file is determined by the user's default visibility/sharing policies.", - "The new file will be visible to only the owner." - ], - "location": "query" - } - }, - "request": { - "$ref": "File" - }, - "response": { - "$ref": "File" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file" - ], - "supportsMediaUpload": true, - "mediaUpload": { - "accept": [ - "*/*" - ], - "maxSize": "5120GB", - "protocols": { - "simple": { - "multipart": true, - "path": "/upload/drive/v2/files" - }, - "resumable": { - "multipart": true, - "path": "/resumable/upload/drive/v2/files" - } - } - }, - "supportsSubscription": true - }, - "list": { - "id": "drive.files.list", - "path": "files", - "httpMethod": "GET", - "description": "Lists the user's files.", - "parameters": { - "corpus": { - "type": "string", - "description": "The body of items (files/documents) to which the query applies.", - "enum": [ - "DEFAULT", - "DOMAIN" - ], - "enumDescriptions": [ - "The items that the user has accessed.", - "Items shared to the user's domain." - ], - "location": "query" - }, - "maxResults": { - "type": "integer", - "description": "Maximum number of files to return.", - "default": "100", - "format": "int32", - "minimum": "0", - "location": "query" - }, - "pageToken": { - "type": "string", - "description": "Page token for files.", - "location": "query" - }, - "projection": { - "type": "string", - "description": "This parameter is deprecated and has no function.", - "enum": [ - "BASIC", - "FULL" - ], - "enumDescriptions": [ - "Deprecated", - "Deprecated" - ], - "location": "query" - }, - "q": { - "type": "string", - "description": "Query string for searching files.", - "location": "query" - } - }, - "response": { - "$ref": "FileList" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "patch": { - "id": "drive.files.patch", - "path": "files/{fileId}", - "httpMethod": "PATCH", - "description": "Updates file metadata and/or content. This method supports patch semantics.", - "parameters": { - "addParents": { - "type": "string", - "description": "Comma-separated list of parent IDs to add.", - "location": "query" - }, - "convert": { - "type": "boolean", - "description": "Whether to convert this file to the corresponding Google Docs format.", - "default": "false", - "location": "query" - }, - "fileId": { - "type": "string", - "description": "The ID of the file to update.", - "required": true, - "location": "path" - }, - "newRevision": { - "type": "boolean", - "description": "Whether a blob upload should create a new revision. If false, the blob data in the current head revision is replaced. If true or not set, a new blob is created as head revision, and previous revisions are preserved (causing increased use of the user's data storage quota).", - "default": "true", - "location": "query" - }, - "ocr": { - "type": "boolean", - "description": "Whether to attempt OCR on .jpg, .png, .gif, or .pdf uploads.", - "default": "false", - "location": "query" - }, - "ocrLanguage": { - "type": "string", - "description": "If ocr is true, hints at the language to use. Valid values are ISO 639-1 codes.", - "location": "query" - }, - "pinned": { - "type": "boolean", - "description": "Whether to pin the new revision. A file can have a maximum of 200 pinned revisions.", - "default": "false", - "location": "query" - }, - "removeParents": { - "type": "string", - "description": "Comma-separated list of parent IDs to remove.", - "location": "query" - }, - "setModifiedDate": { - "type": "boolean", - "description": "Whether to set the modified date with the supplied modified date.", - "default": "false", - "location": "query" - }, - "timedTextLanguage": { - "type": "string", - "description": "The language of the timed text.", - "location": "query" - }, - "timedTextTrackName": { - "type": "string", - "description": "The timed text track name.", - "location": "query" - }, - "updateViewedDate": { - "type": "boolean", - "description": "Whether to update the view date after successfully updating the file.", - "default": "true", - "location": "query" - }, - "useContentAsIndexableText": { - "type": "boolean", - "description": "Whether to use the content as indexable text.", - "default": "false", - "location": "query" - } - }, - "parameterOrder": [ - "fileId" - ], - "request": { - "$ref": "File" - }, - "response": { - "$ref": "File" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.scripts" - ] - }, - "touch": { - "id": "drive.files.touch", - "path": "files/{fileId}/touch", - "httpMethod": "POST", - "description": "Set the file's updated time to the current server time.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file to update.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "response": { - "$ref": "File" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "trash": { - "id": "drive.files.trash", - "path": "files/{fileId}/trash", - "httpMethod": "POST", - "description": "Moves a file to the trash.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file to trash.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "response": { - "$ref": "File" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "untrash": { - "id": "drive.files.untrash", - "path": "files/{fileId}/untrash", - "httpMethod": "POST", - "description": "Restores a file from the trash.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file to untrash.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "response": { - "$ref": "File" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "update": { - "id": "drive.files.update", - "path": "files/{fileId}", - "httpMethod": "PUT", - "description": "Updates file metadata and/or content.", - "parameters": { - "addParents": { - "type": "string", - "description": "Comma-separated list of parent IDs to add.", - "location": "query" - }, - "convert": { - "type": "boolean", - "description": "Whether to convert this file to the corresponding Google Docs format.", - "default": "false", - "location": "query" - }, - "fileId": { - "type": "string", - "description": "The ID of the file to update.", - "required": true, - "location": "path" - }, - "newRevision": { - "type": "boolean", - "description": "Whether a blob upload should create a new revision. If false, the blob data in the current head revision is replaced. If true or not set, a new blob is created as head revision, and previous revisions are preserved (causing increased use of the user's data storage quota).", - "default": "true", - "location": "query" - }, - "ocr": { - "type": "boolean", - "description": "Whether to attempt OCR on .jpg, .png, .gif, or .pdf uploads.", - "default": "false", - "location": "query" - }, - "ocrLanguage": { - "type": "string", - "description": "If ocr is true, hints at the language to use. Valid values are ISO 639-1 codes.", - "location": "query" - }, - "pinned": { - "type": "boolean", - "description": "Whether to pin the new revision. A file can have a maximum of 200 pinned revisions.", - "default": "false", - "location": "query" - }, - "removeParents": { - "type": "string", - "description": "Comma-separated list of parent IDs to remove.", - "location": "query" - }, - "setModifiedDate": { - "type": "boolean", - "description": "Whether to set the modified date with the supplied modified date.", - "default": "false", - "location": "query" - }, - "timedTextLanguage": { - "type": "string", - "description": "The language of the timed text.", - "location": "query" - }, - "timedTextTrackName": { - "type": "string", - "description": "The timed text track name.", - "location": "query" - }, - "updateViewedDate": { - "type": "boolean", - "description": "Whether to update the view date after successfully updating the file.", - "default": "true", - "location": "query" - }, - "useContentAsIndexableText": { - "type": "boolean", - "description": "Whether to use the content as indexable text.", - "default": "false", - "location": "query" - } - }, - "parameterOrder": [ - "fileId" - ], - "request": { - "$ref": "File" - }, - "response": { - "$ref": "File" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.scripts" - ], - "supportsMediaUpload": true, - "mediaUpload": { - "accept": [ - "*/*" - ], - "maxSize": "5120GB", - "protocols": { - "simple": { - "multipart": true, - "path": "/upload/drive/v2/files/{fileId}" - }, - "resumable": { - "multipart": true, - "path": "/resumable/upload/drive/v2/files/{fileId}" - } - } - } - }, - "watch": { - "id": "drive.files.watch", - "path": "files/{fileId}/watch", - "httpMethod": "POST", - "description": "Subscribe to changes on a file", - "parameters": { - "acknowledgeAbuse": { - "type": "boolean", - "description": "Whether the user is acknowledging the risk of downloading known malware or other abusive files.", - "default": "false", - "location": "query" - }, - "fileId": { - "type": "string", - "description": "The ID for the file in question.", - "required": true, - "location": "path" - }, - "projection": { - "type": "string", - "description": "This parameter is deprecated and has no function.", - "enum": [ - "BASIC", - "FULL" - ], - "enumDescriptions": [ - "Deprecated", - "Deprecated" - ], - "location": "query" - }, - "updateViewedDate": { - "type": "boolean", - "description": "Whether to update the view date after successfully retrieving the file.", - "default": "false", - "location": "query" - } - }, - "parameterOrder": [ - "fileId" - ], - "request": { - "$ref": "Channel", - "parameterName": "resource" - }, - "response": { - "$ref": "Channel" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ], - "supportsMediaDownload": true, - "supportsSubscription": true - } - } - }, - "parents": { - "methods": { - "delete": { - "id": "drive.parents.delete", - "path": "files/{fileId}/parents/{parentId}", - "httpMethod": "DELETE", - "description": "Removes a parent from a file.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "parentId": { - "type": "string", - "description": "The ID of the parent.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "parentId" - ], - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "get": { - "id": "drive.parents.get", - "path": "files/{fileId}/parents/{parentId}", - "httpMethod": "GET", - "description": "Gets a specific parent reference.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "parentId": { - "type": "string", - "description": "The ID of the parent.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "parentId" - ], - "response": { - "$ref": "ParentReference" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "insert": { - "id": "drive.parents.insert", - "path": "files/{fileId}/parents", - "httpMethod": "POST", - "description": "Adds a parent folder for a file.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "request": { - "$ref": "ParentReference" - }, - "response": { - "$ref": "ParentReference" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "list": { - "id": "drive.parents.list", - "path": "files/{fileId}/parents", - "httpMethod": "GET", - "description": "Lists a file's parents.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "response": { - "$ref": "ParentList" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - } - } - }, - "permissions": { - "methods": { - "delete": { - "id": "drive.permissions.delete", - "path": "files/{fileId}/permissions/{permissionId}", - "httpMethod": "DELETE", - "description": "Deletes a permission from a file.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID for the file.", - "required": true, - "location": "path" - }, - "permissionId": { - "type": "string", - "description": "The ID for the permission.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "permissionId" - ], - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "get": { - "id": "drive.permissions.get", - "path": "files/{fileId}/permissions/{permissionId}", - "httpMethod": "GET", - "description": "Gets a permission by ID.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID for the file.", - "required": true, - "location": "path" - }, - "permissionId": { - "type": "string", - "description": "The ID for the permission.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "permissionId" - ], - "response": { - "$ref": "Permission" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "getIdForEmail": { - "id": "drive.permissions.getIdForEmail", - "path": "permissionIds/{email}", - "httpMethod": "GET", - "description": "Returns the permission ID for an email address.", - "parameters": { - "email": { - "type": "string", - "description": "The email address for which to return a permission ID", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "email" - ], - "response": { - "$ref": "PermissionId" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.apps.readonly", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "insert": { - "id": "drive.permissions.insert", - "path": "files/{fileId}/permissions", - "httpMethod": "POST", - "description": "Inserts a permission for a file.", - "parameters": { - "emailMessage": { - "type": "string", - "description": "A custom message to include in notification emails.", - "location": "query" - }, - "fileId": { - "type": "string", - "description": "The ID for the file.", - "required": true, - "location": "path" - }, - "sendNotificationEmails": { - "type": "boolean", - "description": "Whether to send notification emails when sharing to users or groups. This parameter is ignored and an email is sent if the role is owner.", - "default": "true", - "location": "query" - } - }, - "parameterOrder": [ - "fileId" - ], - "request": { - "$ref": "Permission" - }, - "response": { - "$ref": "Permission" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "list": { - "id": "drive.permissions.list", - "path": "files/{fileId}/permissions", - "httpMethod": "GET", - "description": "Lists a file's permissions.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID for the file.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "response": { - "$ref": "PermissionList" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "patch": { - "id": "drive.permissions.patch", - "path": "files/{fileId}/permissions/{permissionId}", - "httpMethod": "PATCH", - "description": "Updates a permission. This method supports patch semantics.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID for the file.", - "required": true, - "location": "path" - }, - "permissionId": { - "type": "string", - "description": "The ID for the permission.", - "required": true, - "location": "path" - }, - "transferOwnership": { - "type": "boolean", - "description": "Whether changing a role to 'owner' should also downgrade the current owners to writers.", - "default": "false", - "location": "query" - } - }, - "parameterOrder": [ - "fileId", - "permissionId" - ], - "request": { - "$ref": "Permission" - }, - "response": { - "$ref": "Permission" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "update": { - "id": "drive.permissions.update", - "path": "files/{fileId}/permissions/{permissionId}", - "httpMethod": "PUT", - "description": "Updates a permission.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID for the file.", - "required": true, - "location": "path" - }, - "permissionId": { - "type": "string", - "description": "The ID for the permission.", - "required": true, - "location": "path" - }, - "transferOwnership": { - "type": "boolean", - "description": "Whether changing a role to 'owner' should also downgrade the current owners to writers.", - "default": "false", - "location": "query" - } - }, - "parameterOrder": [ - "fileId", - "permissionId" - ], - "request": { - "$ref": "Permission" - }, - "response": { - "$ref": "Permission" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - } - } - }, - "properties": { - "methods": { - "delete": { - "id": "drive.properties.delete", - "path": "files/{fileId}/properties/{propertyKey}", - "httpMethod": "DELETE", - "description": "Deletes a property.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "propertyKey": { - "type": "string", - "description": "The key of the property.", - "required": true, - "location": "path" - }, - "visibility": { - "type": "string", - "description": "The visibility of the property.", - "default": "private", - "location": "query" - } - }, - "parameterOrder": [ - "fileId", - "propertyKey" - ], - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "get": { - "id": "drive.properties.get", - "path": "files/{fileId}/properties/{propertyKey}", - "httpMethod": "GET", - "description": "Gets a property by its key.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "propertyKey": { - "type": "string", - "description": "The key of the property.", - "required": true, - "location": "path" - }, - "visibility": { - "type": "string", - "description": "The visibility of the property.", - "default": "private", - "location": "query" - } - }, - "parameterOrder": [ - "fileId", - "propertyKey" - ], - "response": { - "$ref": "Property" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "insert": { - "id": "drive.properties.insert", - "path": "files/{fileId}/properties", - "httpMethod": "POST", - "description": "Adds a property to a file.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "request": { - "$ref": "Property" - }, - "response": { - "$ref": "Property" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "list": { - "id": "drive.properties.list", - "path": "files/{fileId}/properties", - "httpMethod": "GET", - "description": "Lists a file's properties.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "response": { - "$ref": "PropertyList" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "patch": { - "id": "drive.properties.patch", - "path": "files/{fileId}/properties/{propertyKey}", - "httpMethod": "PATCH", - "description": "Updates a property. This method supports patch semantics.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "propertyKey": { - "type": "string", - "description": "The key of the property.", - "required": true, - "location": "path" - }, - "visibility": { - "type": "string", - "description": "The visibility of the property.", - "default": "private", - "location": "query" - } - }, - "parameterOrder": [ - "fileId", - "propertyKey" - ], - "request": { - "$ref": "Property" - }, - "response": { - "$ref": "Property" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "update": { - "id": "drive.properties.update", - "path": "files/{fileId}/properties/{propertyKey}", - "httpMethod": "PUT", - "description": "Updates a property.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "propertyKey": { - "type": "string", - "description": "The key of the property.", - "required": true, - "location": "path" - }, - "visibility": { - "type": "string", - "description": "The visibility of the property.", - "default": "private", - "location": "query" - } - }, - "parameterOrder": [ - "fileId", - "propertyKey" - ], - "request": { - "$ref": "Property" - }, - "response": { - "$ref": "Property" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file" - ] - } - } - }, - "realtime": { - "methods": { - "get": { - "id": "drive.realtime.get", - "path": "files/{fileId}/realtime", - "httpMethod": "GET", - "description": "Exports the contents of the Realtime API data model associated with this file as JSON.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file that the Realtime API data model is associated with.", - "required": true, - "location": "path" - }, - "revision": { - "type": "integer", - "description": "The revision of the Realtime API data model to export. Revisions start at 1 (the initial empty data model) and are incremented with each change. If this parameter is excluded, the most recent data model will be returned.", - "format": "int32", - "minimum": "1", - "location": "query" - } - }, - "parameterOrder": [ - "fileId" - ], - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.readonly" - ], - "supportsMediaDownload": true - }, - "update": { - "id": "drive.realtime.update", - "path": "files/{fileId}/realtime", - "httpMethod": "PUT", - "description": "Overwrites the Realtime API data model associated with this file with the provided JSON data model.", - "parameters": { - "baseRevision": { - "type": "string", - "description": "The revision of the model to diff the uploaded model against. If set, the uploaded model is diffed against the provided revision and those differences are merged with any changes made to the model after the provided revision. If not set, the uploaded model replaces the current model on the server.", - "location": "query" - }, - "fileId": { - "type": "string", - "description": "The ID of the file that the Realtime API data model is associated with.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ], - "supportsMediaUpload": true, - "mediaUpload": { - "accept": [ - "*/*" - ], - "maxSize": "10MB", - "protocols": { - "simple": { - "multipart": true, - "path": "/upload/drive/v2/files/{fileId}/realtime" - }, - "resumable": { - "multipart": true, - "path": "/resumable/upload/drive/v2/files/{fileId}/realtime" - } - } - } - } - } - }, - "replies": { - "methods": { - "delete": { - "id": "drive.replies.delete", - "path": "files/{fileId}/comments/{commentId}/replies/{replyId}", - "httpMethod": "DELETE", - "description": "Deletes a reply.", - "parameters": { - "commentId": { - "type": "string", - "description": "The ID of the comment.", - "required": true, - "location": "path" - }, - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "replyId": { - "type": "string", - "description": "The ID of the reply.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "commentId", - "replyId" - ], - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "get": { - "id": "drive.replies.get", - "path": "files/{fileId}/comments/{commentId}/replies/{replyId}", - "httpMethod": "GET", - "description": "Gets a reply.", - "parameters": { - "commentId": { - "type": "string", - "description": "The ID of the comment.", - "required": true, - "location": "path" - }, - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "includeDeleted": { - "type": "boolean", - "description": "If set, this will succeed when retrieving a deleted reply.", - "default": "false", - "location": "query" - }, - "replyId": { - "type": "string", - "description": "The ID of the reply.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "commentId", - "replyId" - ], - "response": { - "$ref": "CommentReply" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "insert": { - "id": "drive.replies.insert", - "path": "files/{fileId}/comments/{commentId}/replies", - "httpMethod": "POST", - "description": "Creates a new reply to the given comment.", - "parameters": { - "commentId": { - "type": "string", - "description": "The ID of the comment.", - "required": true, - "location": "path" - }, - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "commentId" - ], - "request": { - "$ref": "CommentReply" - }, - "response": { - "$ref": "CommentReply" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "list": { - "id": "drive.replies.list", - "path": "files/{fileId}/comments/{commentId}/replies", - "httpMethod": "GET", - "description": "Lists all of the replies to a comment.", - "parameters": { - "commentId": { - "type": "string", - "description": "The ID of the comment.", - "required": true, - "location": "path" - }, - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "includeDeleted": { - "type": "boolean", - "description": "If set, all replies, including deleted replies (with content stripped) will be returned.", - "default": "false", - "location": "query" - }, - "maxResults": { - "type": "integer", - "description": "The maximum number of replies to include in the response, used for paging.", - "default": "20", - "format": "int32", - "minimum": "0", - "maximum": "100", - "location": "query" - }, - "pageToken": { - "type": "string", - "description": "The continuation token, used to page through large result sets. To get the next page of results, set this parameter to the value of \"nextPageToken\" from the previous response.", - "location": "query" - } - }, - "parameterOrder": [ - "fileId", - "commentId" - ], - "response": { - "$ref": "CommentReplyList" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "patch": { - "id": "drive.replies.patch", - "path": "files/{fileId}/comments/{commentId}/replies/{replyId}", - "httpMethod": "PATCH", - "description": "Updates an existing reply. This method supports patch semantics.", - "parameters": { - "commentId": { - "type": "string", - "description": "The ID of the comment.", - "required": true, - "location": "path" - }, - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "replyId": { - "type": "string", - "description": "The ID of the reply.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "commentId", - "replyId" - ], - "request": { - "$ref": "CommentReply" - }, - "response": { - "$ref": "CommentReply" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "update": { - "id": "drive.replies.update", - "path": "files/{fileId}/comments/{commentId}/replies/{replyId}", - "httpMethod": "PUT", - "description": "Updates an existing reply.", - "parameters": { - "commentId": { - "type": "string", - "description": "The ID of the comment.", - "required": true, - "location": "path" - }, - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "replyId": { - "type": "string", - "description": "The ID of the reply.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "commentId", - "replyId" - ], - "request": { - "$ref": "CommentReply" - }, - "response": { - "$ref": "CommentReply" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.file" - ] - } - } - }, - "revisions": { - "methods": { - "delete": { - "id": "drive.revisions.delete", - "path": "files/{fileId}/revisions/{revisionId}", - "httpMethod": "DELETE", - "description": "Removes a revision.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "revisionId": { - "type": "string", - "description": "The ID of the revision.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "revisionId" - ], - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "get": { - "id": "drive.revisions.get", - "path": "files/{fileId}/revisions/{revisionId}", - "httpMethod": "GET", - "description": "Gets a specific revision.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - }, - "revisionId": { - "type": "string", - "description": "The ID of the revision.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "revisionId" - ], - "response": { - "$ref": "Revision" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "list": { - "id": "drive.revisions.list", - "path": "files/{fileId}/revisions", - "httpMethod": "GET", - "description": "Lists a file's revisions.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID of the file.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId" - ], - "response": { - "$ref": "RevisionList" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/drive.metadata.readonly", - "https://www.googleapis.com/auth/drive.readonly" - ] - }, - "patch": { - "id": "drive.revisions.patch", - "path": "files/{fileId}/revisions/{revisionId}", - "httpMethod": "PATCH", - "description": "Updates a revision. This method supports patch semantics.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID for the file.", - "required": true, - "location": "path" - }, - "revisionId": { - "type": "string", - "description": "The ID for the revision.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "revisionId" - ], - "request": { - "$ref": "Revision" - }, - "response": { - "$ref": "Revision" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file" - ] - }, - "update": { - "id": "drive.revisions.update", - "path": "files/{fileId}/revisions/{revisionId}", - "httpMethod": "PUT", - "description": "Updates a revision.", - "parameters": { - "fileId": { - "type": "string", - "description": "The ID for the file.", - "required": true, - "location": "path" - }, - "revisionId": { - "type": "string", - "description": "The ID for the revision.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "fileId", - "revisionId" - ], - "request": { - "$ref": "Revision" - }, - "response": { - "$ref": "Revision" - }, - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.appdata", - "https://www.googleapis.com/auth/drive.file" - ] - } - } - } - } -} diff --git a/_third_party/code.google.com/p/google-api-go-client/googleapi/googleapi_test.go b/_third_party/code.google.com/p/google-api-go-client/googleapi/googleapi_test.go deleted file mode 100644 index abc5185..0000000 --- a/_third_party/code.google.com/p/google-api-go-client/googleapi/googleapi_test.go +++ /dev/null @@ -1,361 +0,0 @@ -// Copyright 2011 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package googleapi - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "reflect" - "strings" - "testing" -) - -type SetOpaqueTest struct { - in *url.URL - wantRequestURI string -} - -var setOpaqueTests = []SetOpaqueTest{ - // no path - { - &url.URL{ - Scheme: "http", - Host: "www.golang.org", - }, - "http://www.golang.org", - }, - // path - { - &url.URL{ - Scheme: "http", - Host: "www.golang.org", - Path: "/", - }, - "http://www.golang.org/", - }, - // file with hex escaping - { - &url.URL{ - Scheme: "https", - Host: "www.golang.org", - Path: "/file%20one&two", - }, - "https://www.golang.org/file%20one&two", - }, - // query - { - &url.URL{ - Scheme: "http", - Host: "www.golang.org", - Path: "/", - RawQuery: "q=go+language", - }, - "http://www.golang.org/?q=go+language", - }, - // file with hex escaping in path plus query - { - &url.URL{ - Scheme: "https", - Host: "www.golang.org", - Path: "/file%20one&two", - RawQuery: "q=go+language", - }, - "https://www.golang.org/file%20one&two?q=go+language", - }, - // query with hex escaping - { - &url.URL{ - Scheme: "http", - Host: "www.golang.org", - Path: "/", - RawQuery: "q=go%20language", - }, - "http://www.golang.org/?q=go%20language", - }, -} - -// prefixTmpl is a template for the expected prefix of the output of writing -// an HTTP request. -const prefixTmpl = "GET %v HTTP/1.1\r\nHost: %v\r\n" - -func TestSetOpaque(t *testing.T) { - for _, test := range setOpaqueTests { - u := *test.in - SetOpaque(&u) - - w := &bytes.Buffer{} - r := &http.Request{URL: &u} - if err := r.Write(w); err != nil { - t.Errorf("write request: %v", err) - continue - } - - prefix := fmt.Sprintf(prefixTmpl, test.wantRequestURI, test.in.Host) - if got := string(w.Bytes()); !strings.HasPrefix(got, prefix) { - t.Errorf("got %q expected prefix %q", got, prefix) - } - } -} - -type ExpandTest struct { - in string - expansions map[string]string - want string -} - -var expandTests = []ExpandTest{ - // no expansions - { - "http://www.golang.org/", - map[string]string{}, - "http://www.golang.org/", - }, - // one expansion, no escaping - { - "http://www.golang.org/{bucket}/delete", - map[string]string{ - "bucket": "red", - }, - "http://www.golang.org/red/delete", - }, - // one expansion, with hex escapes - { - "http://www.golang.org/{bucket}/delete", - map[string]string{ - "bucket": "red/blue", - }, - "http://www.golang.org/red%2Fblue/delete", - }, - // one expansion, with space - { - "http://www.golang.org/{bucket}/delete", - map[string]string{ - "bucket": "red or blue", - }, - "http://www.golang.org/red%20or%20blue/delete", - }, - // expansion not found - { - "http://www.golang.org/{object}/delete", - map[string]string{ - "bucket": "red or blue", - }, - "http://www.golang.org//delete", - }, - // multiple expansions - { - "http://www.golang.org/{one}/{two}/{three}/get", - map[string]string{ - "one": "ONE", - "two": "TWO", - "three": "THREE", - }, - "http://www.golang.org/ONE/TWO/THREE/get", - }, - // utf-8 characters - { - "http://www.golang.org/{bucket}/get", - map[string]string{ - "bucket": "£100", - }, - "http://www.golang.org/%C2%A3100/get", - }, - // punctuations - { - "http://www.golang.org/{bucket}/get", - map[string]string{ - "bucket": `/\@:,.`, - }, - "http://www.golang.org/%2F%5C%40%3A%2C./get", - }, - // mis-matched brackets - { - "http://www.golang.org/{bucket/get", - map[string]string{ - "bucket": "red", - }, - "http://www.golang.org/{bucket/get", - }, - // "+" prefix for suppressing escape - // See also: http://tools.ietf.org/html/rfc6570#section-3.2.3 - { - "http://www.golang.org/{+topic}", - map[string]string{ - "topic": "/topics/myproject/mytopic", - }, - // The double slashes here look weird, but it's intentional - "http://www.golang.org//topics/myproject/mytopic", - }, -} - -func TestExpand(t *testing.T) { - for i, test := range expandTests { - u := url.URL{ - Path: test.in, - } - Expand(&u, test.expansions) - got := u.Path - if got != test.want { - t.Errorf("got %q expected %q in test %d", got, test.want, i+1) - } - } -} - -type CheckResponseTest struct { - in *http.Response - bodyText string - want error - errText string -} - -var checkResponseTests = []CheckResponseTest{ - { - &http.Response{ - StatusCode: http.StatusOK, - }, - "", - nil, - "", - }, - { - &http.Response{ - StatusCode: http.StatusInternalServerError, - }, - `{"error":{}}`, - &Error{ - Code: http.StatusInternalServerError, - Body: `{"error":{}}`, - }, - `googleapi: got HTTP response code 500 with body: {"error":{}}`, - }, - { - &http.Response{ - StatusCode: http.StatusNotFound, - }, - `{"error":{"message":"Error message for StatusNotFound."}}`, - &Error{ - Code: http.StatusNotFound, - Message: "Error message for StatusNotFound.", - Body: `{"error":{"message":"Error message for StatusNotFound."}}`, - }, - "googleapi: Error 404: Error message for StatusNotFound.", - }, - { - &http.Response{ - StatusCode: http.StatusBadRequest, - }, - `{"error":"invalid_token","error_description":"Invalid Value"}`, - &Error{ - Code: http.StatusBadRequest, - Body: `{"error":"invalid_token","error_description":"Invalid Value"}`, - }, - `googleapi: got HTTP response code 400 with body: {"error":"invalid_token","error_description":"Invalid Value"}`, - }, - { - &http.Response{ - StatusCode: http.StatusBadRequest, - }, - `{"error":{"errors":[{"domain":"usageLimits","reason":"keyInvalid","message":"Bad Request"}],"code":400,"message":"Bad Request"}}`, - &Error{ - Code: http.StatusBadRequest, - Errors: []ErrorItem{ - { - Reason: "keyInvalid", - Message: "Bad Request", - }, - }, - Body: `{"error":{"errors":[{"domain":"usageLimits","reason":"keyInvalid","message":"Bad Request"}],"code":400,"message":"Bad Request"}}`, - Message: "Bad Request", - }, - "googleapi: Error 400: Bad Request, keyInvalid", - }, -} - -func TestCheckResponse(t *testing.T) { - for _, test := range checkResponseTests { - res := test.in - if test.bodyText != "" { - res.Body = ioutil.NopCloser(strings.NewReader(test.bodyText)) - } - g := CheckResponse(res) - if !reflect.DeepEqual(g, test.want) { - t.Errorf("CheckResponse: got %v, want %v", g, test.want) - gotJson, err := json.Marshal(g) - if err != nil { - t.Error(err) - } - wantJson, err := json.Marshal(test.want) - if err != nil { - t.Error(err) - } - t.Errorf("json(got): %q\njson(want): %q", string(gotJson), string(wantJson)) - } - if g != nil && g.Error() != test.errText { - t.Errorf("CheckResponse: unexpected error message.\nGot: %q\nwant: %q", g, test.errText) - } - } -} - -type VariantPoint struct { - Type string - Coordinates []float64 -} - -type VariantTest struct { - in map[string]interface{} - result bool - want VariantPoint -} - -var coords = []interface{}{1.0, 2.0} - -var variantTests = []VariantTest{ - { - in: map[string]interface{}{ - "type": "Point", - "coordinates": coords, - }, - result: true, - want: VariantPoint{ - Type: "Point", - Coordinates: []float64{1.0, 2.0}, - }, - }, - { - in: map[string]interface{}{ - "type": "Point", - "bogus": coords, - }, - result: true, - want: VariantPoint{ - Type: "Point", - }, - }, -} - -func TestVariantType(t *testing.T) { - for _, test := range variantTests { - if g := VariantType(test.in); g != test.want.Type { - t.Errorf("VariantType(%v): got %v, want %v", test.in, g, test.want.Type) - } - } -} - -func TestConvertVariant(t *testing.T) { - for _, test := range variantTests { - g := VariantPoint{} - r := ConvertVariant(test.in, &g) - if r != test.result { - t.Errorf("ConvertVariant(%v): got %v, want %v", test.in, r, test.result) - } - if !reflect.DeepEqual(g, test.want) { - t.Errorf("ConvertVariant(%v): got %v, want %v", test.in, g, test.want) - } - } -} diff --git a/_third_party/code.google.com/p/google-api-go-client/googleapi/internal/uritemplates/LICENSE b/_third_party/code.google.com/p/google-api-go-client/googleapi/internal/uritemplates/LICENSE deleted file mode 100644 index de9c88c..0000000 --- a/_third_party/code.google.com/p/google-api-go-client/googleapi/internal/uritemplates/LICENSE +++ /dev/null @@ -1,18 +0,0 @@ -Copyright (c) 2013 Joshua Tacoma - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/_third_party/code.google.com/p/google-api-go-client/googleapi/types_test.go b/_third_party/code.google.com/p/google-api-go-client/googleapi/types_test.go deleted file mode 100644 index a6b2045..0000000 --- a/_third_party/code.google.com/p/google-api-go-client/googleapi/types_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2013 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package googleapi - -import ( - "encoding/json" - "reflect" - "testing" -) - -func TestTypes(t *testing.T) { - type T struct { - I32 Int32s - I64 Int64s - U32 Uint32s - U64 Uint64s - F64 Float64s - } - v := &T{ - I32: Int32s{-1, 2, 3}, - I64: Int64s{-1, 2, 1 << 33}, - U32: Uint32s{1, 2}, - U64: Uint64s{1, 2, 1 << 33}, - F64: Float64s{1.5, 3.33}, - } - got, err := json.Marshal(v) - if err != nil { - t.Fatal(err) - } - want := `{"I32":["-1","2","3"],"I64":["-1","2","8589934592"],"U32":["1","2"],"U64":["1","2","8589934592"],"F64":["1.5","3.33"]}` - if string(got) != want { - t.Fatalf("Marshal mismatch.\n got: %s\nwant: %s\n", got, want) - } - - v2 := new(T) - if err := json.Unmarshal(got, v2); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if !reflect.DeepEqual(v, v2) { - t.Fatalf("Unmarshal didn't produce same results.\n got: %#v\nwant: %#v\n", v, v2) - } -} diff --git a/_third_party/code.google.com/p/portaudio-go/portaudio/README b/_third_party/code.google.com/p/portaudio-go/portaudio/README deleted file mode 100644 index 506dace..0000000 --- a/_third_party/code.google.com/p/portaudio-go/portaudio/README +++ /dev/null @@ -1,7 +0,0 @@ -This package provides an interface to the PortAudio audio I/O library (http://www.portaudio.com/). See the package documentation for details. - -To build portaudio-go, you must first have the PortAudio development headers and libraries installed. Some systems provide a package for this; e.g., on Ubuntu you would want to run `apt-get install portaudio19-dev`. On other systems you might have to install from source. - -A previous version of OpenDefaultStream automatically called Initialize (and Stream.Close called Terminate). This behavior no longer exists; clients must explicitly call Initialize before making any other calls (and finally Terminate). - -Thanks to sqweek for motivating and contributing to host API and device enumeration. diff --git a/_third_party/code.google.com/p/portaudio-go/portaudio/pa.c b/_third_party/code.google.com/p/portaudio-go/portaudio/pa.c deleted file mode 100644 index d383504..0000000 --- a/_third_party/code.google.com/p/portaudio-go/portaudio/pa.c +++ /dev/null @@ -1,9 +0,0 @@ -#include "_cgo_export.h" - -int cb(const void *inputBuffer, void *outputBuffer, unsigned long frames, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { - streamCallback((void*)inputBuffer, outputBuffer, frames, (PaStreamCallbackTimeInfo*)timeInfo, statusFlags, userData); - return paContinue; -} - -//using a variable ensures that the callback signature is checked -PaStreamCallback* paStreamCallback = cb; diff --git a/_third_party/github.com/boltdb/bolt/LICENSE b/_third_party/github.com/boltdb/bolt/LICENSE deleted file mode 100644 index 004e77f..0000000 --- a/_third_party/github.com/boltdb/bolt/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 Ben Johnson - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/_third_party/github.com/boltdb/bolt/Makefile b/_third_party/github.com/boltdb/bolt/Makefile deleted file mode 100644 index cfbed51..0000000 --- a/_third_party/github.com/boltdb/bolt/Makefile +++ /dev/null @@ -1,54 +0,0 @@ -TEST=. -BENCH=. -COVERPROFILE=/tmp/c.out -BRANCH=`git rev-parse --abbrev-ref HEAD` -COMMIT=`git rev-parse --short HEAD` -GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)" - -default: build - -bench: - go test -v -test.run=NOTHINCONTAINSTHIS -test.bench=$(BENCH) - -# http://cloc.sourceforge.net/ -cloc: - @cloc --not-match-f='Makefile|_test.go' . - -cover: fmt - go test -coverprofile=$(COVERPROFILE) -test.run=$(TEST) $(COVERFLAG) . - go tool cover -html=$(COVERPROFILE) - rm $(COVERPROFILE) - -cpuprofile: fmt - @go test -c - @./bolt.test -test.v -test.run=$(TEST) -test.cpuprofile cpu.prof - -# go get github.com/kisielk/errcheck -errcheck: - @echo "=== errcheck ===" - @errcheck github.com/boltdb/bolt - -fmt: - @go fmt ./... - -get: - @go get -d ./... - -build: get - @mkdir -p bin - @go build -ldflags=$(GOLDFLAGS) -a -o bin/bolt ./cmd/bolt - -test: fmt - @go get github.com/stretchr/testify/assert - @echo "=== TESTS ===" - @go test -v -cover -test.run=$(TEST) - @echo "" - @echo "" - @echo "=== CLI ===" - @go test -v -test.run=$(TEST) ./cmd/bolt - @echo "" - @echo "" - @echo "=== RACE DETECTOR ===" - @go test -v -race -test.run="TestSimulate_(100op|1000op)" - -.PHONY: bench cloc cover cpuprofile fmt memprofile test diff --git a/_third_party/github.com/boltdb/bolt/README.md b/_third_party/github.com/boltdb/bolt/README.md deleted file mode 100644 index cb65702..0000000 --- a/_third_party/github.com/boltdb/bolt/README.md +++ /dev/null @@ -1,531 +0,0 @@ -Bolt [![Build Status](https://drone.io/github.com/boltdb/bolt/status.png)](https://drone.io/github.com/boltdb/bolt/latest) [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.png?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.png)](https://godoc.org/github.com/boltdb/bolt) ![Version](http://img.shields.io/badge/version-1.0-green.png) -==== - -Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas] and -the [LMDB project][lmdb]. The goal of the project is to provide a simple, -fast, and reliable database for projects that don't require a full database -server such as Postgres or MySQL. - -Since Bolt is meant to be used as such a low-level piece of functionality, -simplicity is key. The API will be small and only focus on getting values -and setting values. That's it. - -[hyc_symas]: https://twitter.com/hyc_symas -[lmdb]: http://symas.com/mdb/ - - -## Project Status - -Bolt is stable and the API is fixed. Full unit test coverage and randomized -black box testing are used to ensure database consistency and thread safety. -Bolt is currently in high-load production environments serving databases as -large as 1TB. Many companies such as Shopify and Heroku use Bolt-backed -services every day. - - -## Getting Started - -### Installing - -To start using Bolt, install Go and run `go get`: - -```sh -$ go get github.com/boltdb/bolt/... -``` - -This will retrieve the library and install the `bolt` command line utility into -your `$GOBIN` path. - - -### Opening a database - -The top-level object in Bolt is a `DB`. It is represented as a single file on -your disk and represents a consistent snapshot of your data. - -To open your database, simply use the `bolt.Open()` function: - -```go -package main - -import ( - "log" - - "github.com/boltdb/bolt" -) - -func main() { - // Open the my.db data file in your current directory. - // It will be created if it doesn't exist. - db, err := bolt.Open("my.db", 0600, nil) - if err != nil { - log.Fatal(err) - } - defer db.Close() - - ... -} -``` - -Please note that Bolt obtains a file lock on the data file so multiple processes -cannot open the same database at the same time. Opening an already open Bolt -database will cause it to hang until the other process closes it. To prevent -an indefinite wait you can pass a timeout option to the `Open()` function: - -```go -db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second}) -``` - - -### Transactions - -Bolt allows only one read-write transaction at a time but allows as many -read-only transactions as you want at a time. Each transaction has a consistent -view of the data as it existed when the transaction started. - -Individual transactions and all objects created from them (e.g. buckets, keys) -are not thread safe. To work with data in multiple goroutines you must start -a transaction for each one or use locking to ensure only one goroutine accesses -a transaction at a time. Creating transaction from the `DB` is thread safe. - - -#### Read-write transactions - -To start a read-write transaction, you can use the `DB.Update()` function: - -```go -err := db.Update(func(tx *bolt.Tx) error { - ... - return nil -}) -``` - -Inside the closure, you have a consistent view of the database. You commit the -transaction by returning `nil` at the end. You can also rollback the transaction -at any point by returning an error. All database operations are allowed inside -a read-write transaction. - -Always check the return error as it will report any disk failures that can cause -your transaction to not complete. If you return an error within your closure -it will be passed through. - - -#### Read-only transactions - -To start a read-only transaction, you can use the `DB.View()` function: - -```go -err := db.View(func(tx *bolt.Tx) error { - ... - return nil -}) -``` - -You also get a consistent view of the database within this closure, however, -no mutating operations are allowed within a read-only transaction. You can only -retrieve buckets, retrieve values, and copy the database within a read-only -transaction. - -#### Managing transactions manually - -The `DB.View()` and `DB.Update()` functions are wrappers around the `DB.Begin()` -function. These helper functions will start the transaction, execute a function, -and then safely close your transaction if an error is returned. This is the -recommended way to use Bolt transactions. - -However, sometimes you may want to manually start and end your transactions. -You can use the `Tx.Begin()` function directly but _please_ be sure to close the -transaction. - -```go -// Start a writable transaction. -tx, err := db.Begin(true) -if err != nil { - return err -} -defer tx.Rollback() - -// Use the transaction... -_, err := tx.CreateBucket([]byte("MyBucket") -if err != nil { - return err -} - -// Commit the transaction and check for error. -if err := tx.Commit(); err != nil { - return err -} -``` - -The first argument to `DB.Begin()` is a boolean stating if the transaction -should be writable. - - -### Using buckets - -Buckets are collections of key/value pairs within the database. All keys in a -bucket must be unique. You can create a bucket using the `DB.CreateBucket()` -function: - -```go -db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucket([]byte("MyBucket")) - if err != nil { - return fmt.Errorf("create bucket: %s", err) - } - return nil -}) -``` - -You can also create a bucket only if it doesn't exist by using the -`Tx.CreateBucketIfNotExists()` function. It's a common pattern to call this -function for all your top-level buckets after you open your database so you can -guarantee that they exist for future transactions. - -To delete a bucket, simply call the `Tx.DeleteBucket()` function. - - -### Using key/value pairs - -To save a key/value pair to a bucket, use the `Bucket.Put()` function: - -```go -db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("MyBucket")) - err := b.Put([]byte("answer"), []byte("42")) - return err -}) -``` - -This will set the value of the `"answer"` key to `"42"` in the `MyBucket` -bucket. To retrieve this value, we can use the `Bucket.Get()` function: - -```go -db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("MyBucket")) - v := b.Get([]byte("answer")) - fmt.Printf("The answer is: %s\n", v) - return nil -}) -``` - -The `Get()` function does not return an error because its operation is -guarenteed to work (unless there is some kind of system failure). If the key -exists then it will return its byte slice value. If it doesn't exist then it -will return `nil`. It's important to note that you can have a zero-length value -set to a key which is different than the key not existing. - -Use the `Bucket.Delete()` function to delete a key from the bucket. - - -### Iterating over keys - -Bolt stores its keys in byte-sorted order within a bucket. This makes sequential -iteration over these keys extremely fast. To iterate over keys we'll use a -`Cursor`: - -```go -db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("MyBucket")) - c := b.Cursor() - - for k, v := c.First(); k != nil; k, v = c.Next() { - fmt.Printf("key=%s, value=%s\n", k, v) - } - - return nil -}) -``` - -The cursor allows you to move to a specific point in the list of keys and move -forward or backward through the keys one at a time. - -The following functions are available on the cursor: - -``` -First() Move to the first key. -Last() Move to the last key. -Seek() Move to a specific key. -Next() Move to the next key. -Prev() Move to the previous key. -``` - -When you have iterated to the end of the cursor then `Next()` will return `nil`. -You must seek to a position using `First()`, `Last()`, or `Seek()` before -calling `Next()` or `Prev()`. If you do not seek to a position then these -functions will return `nil`. - - -#### Prefix scans - -To iterate over a key prefix, you can combine `Seek()` and `bytes.HasPrefix()`: - -```go -db.View(func(tx *bolt.Tx) error { - c := tx.Bucket([]byte("MyBucket")).Cursor() - - prefix := []byte("1234") - for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() { - fmt.Printf("key=%s, value=%s\n", k, v) - } - - return nil -}) -``` - -#### Range scans - -Another common use case is scanning over a range such as a time range. If you -use a sortable time encoding such as RFC3339 then you can query a specific -date range like this: - -```go -db.View(func(tx *bolt.Tx) error { - // Assume our events bucket has RFC3339 encoded time keys. - c := tx.Bucket([]byte("Events")).Cursor() - - // Our time range spans the 90's decade. - min := []byte("1990-01-01T00:00:00Z") - max := []byte("2000-01-01T00:00:00Z") - - // Iterate over the 90's. - for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() { - fmt.Printf("%s: %s\n", k, v) - } - - return nil -}) -``` - - -#### ForEach() - -You can also use the function `ForEach()` if you know you'll be iterating over -all the keys in a bucket: - -```go -db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("MyBucket")) - b.ForEach(func(k, v []byte) error { - fmt.Printf("key=%s, value=%s\n", k, v) - return nil - }) - return nil -}) -``` - - -### Nested buckets - -You can also store a bucket in a key to create nested buckets. The API is the -same as the bucket management API on the `DB` object: - -```go -func (*Bucket) CreateBucket(key []byte) (*Bucket, error) -func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) -func (*Bucket) DeleteBucket(key []byte) error -``` - - -### Database backups - -Bolt is a single file so it's easy to backup. You can use the `Tx.Copy()` -function to write a consistent view of the database to a writer. If you call -this from a read-only transaction, it will perform a hot backup and not block -your other database reads and writes. It will also use `O_DIRECT` when available -to prevent page cache trashing. - -One common use case is to backup over HTTP so you can use tools like `cURL` to -do database backups: - -```go -func BackupHandleFunc(w http.ResponseWriter, req *http.Request) { - err := db.View(func(tx bolt.Tx) error { - w.Header().Set("Content-Type", "application/octet-stream") - w.Header().Set("Content-Disposition", `attachment; filename="my.db"`) - w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) - return tx.Copy(w) - }) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } -} -``` - -Then you can backup using this command: - -```sh -$ curl http://localhost/backup > my.db -``` - -Or you can open your browser to `http://localhost/backup` and it will download -automatically. - -If you want to backup to another file you can use the `Tx.CopyFile()` helper -function. - - -### Statistics - -The database keeps a running count of many of the internal operations it -performs so you can better understand what's going on. By grabbing a snapshot -of these stats at two points in time we can see what operations were performed -in that time range. - -For example, we could start a goroutine to log stats every 10 seconds: - -```go -go func() { - // Grab the initial stats. - prev := db.Stats() - - for { - // Wait for 10s. - time.Sleep(10 * time.Second) - - // Grab the current stats and diff them. - stats := db.Stats() - diff := stats.Sub(&prev) - - // Encode stats to JSON and print to STDERR. - json.NewEncoder(os.Stderr).Encode(diff) - - // Save stats for the next loop. - prev = stats - } -}() -``` - -It's also useful to pipe these stats to a service such as statsd for monitoring -or to provide an HTTP endpoint that will perform a fixed-length sample. - - -## Resources - -For more information on getting started with Bolt, check out the following articles: - -* [Intro to BoltDB: Painless Performant Persistence](http://npf.io/2014/07/intro-to-boltdb-painless-performant-persistence/) by [Nate Finch](https://github.com/natefinch). - - - -## Comparison with other databases - -### Postgres, MySQL, & other relational databases - -Relational databases structure data into rows and are only accessible through -the use of SQL. This approach provides flexibility in how you store and query -your data but also incurs overhead in parsing and planning SQL statements. Bolt -accesses all data by a byte slice key. This makes Bolt fast to read and write -data by key but provides no built-in support for joining values together. - -Most relational databases (with the exception of SQLite) are standalone servers -that run separately from your application. This gives your systems -flexibility to connect multiple application servers to a single database -server but also adds overhead in serializing and transporting data over the -network. Bolt runs as a library included in your application so all data access -has to go through your application's process. This brings data closer to your -application but limits multi-process access to the data. - - -### LevelDB, RocksDB - -LevelDB and its derivatives (RocksDB, HyperLevelDB) are similar to Bolt in that -they are libraries bundled into the application, however, their underlying -structure is a log-structured merge-tree (LSM tree). An LSM tree optimizes -random writes by using a write ahead log and multi-tiered, sorted files called -SSTables. Bolt uses a B+tree internally and only a single file. Both approaches -have trade offs. - -If you require a high random write throughput (>10,000 w/sec) or you need to use -spinning disks then LevelDB could be a good choice. If your application is -read-heavy or does a lot of range scans then Bolt could be a good choice. - -One other important consideration is that LevelDB does not have transactions. -It supports batch writing of key/values pairs and it supports read snapshots -but it will not give you the ability to do a compare-and-swap operation safely. -Bolt supports fully serializable ACID transactions. - - -### LMDB - -Bolt was originally a port of LMDB so it is architecturally similar. Both use -a B+tree, have ACID semantics with fully serializable transactions, and support -lock-free MVCC using a single writer and multiple readers. - -The two projects have somewhat diverged. LMDB heavily focuses on raw performance -while Bolt has focused on simplicity and ease of use. For example, LMDB allows -several unsafe actions such as direct writes for the sake of performance. Bolt -opts to disallow actions which can leave the database in a corrupted state. The -only exception to this in Bolt is `DB.NoSync`. - -There are also a few differences in API. LMDB requires a maximum mmap size when -opening an `mdb_env` whereas Bolt will handle incremental mmap resizing -automatically. LMDB overloads the getter and setter functions with multiple -flags whereas Bolt splits these specialized cases into their own functions. - - -## Caveats & Limitations - -It's important to pick the right tool for the job and Bolt is no exception. -Here are a few things to note when evaluating and using Bolt: - -* Bolt is good for read intensive workloads. Sequential write performance is - also fast but random writes can be slow. You can add a write-ahead log or - [transaction coalescer](https://github.com/boltdb/coalescer) in front of Bolt - to mitigate this issue. - -* Bolt uses a B+tree internally so there can be a lot of random page access. - SSDs provide a significant performance boost over spinning disks. - -* Try to avoid long running read transactions. Bolt uses copy-on-write so - old pages cannot be reclaimed while an old transaction is using them. - -* Byte slices returned from Bolt are only valid during a transaction. Once the - transaction has been committed or rolled back then the memory they point to - can be reused by a new page or can be unmapped from virtual memory and you'll - see an `unexpected fault address` panic when accessing it. - -* Be careful when using `Bucket.FillPercent`. Setting a high fill percent for - buckets that have random inserts will cause your database to have very poor - page utilization. - -* Use larger buckets in general. Smaller buckets causes poor page utilization - once they become larger than the page size (typically 4KB). - -* Bulk loading a lot of random writes into a new bucket can be slow as the - page will not split until the transaction is committed. Randomly inserting - more than 100,000 key/value pairs into a single new bucket in a single - transaction is not advised. - -* Bolt uses a memory-mapped file so the underlying operating system handles the - caching of the data. Typically, the OS will cache as much of the file as it - can in memory and will release memory as needed to other processes. This means - that Bolt can show very high memory usage when working with large databases. - However, this is expected and the OS will release memory as needed. Bolt can - handle databases much larger than the available physical RAM. - - -## Other Projects Using Bolt - -Below is a list of public, open source projects that use Bolt: - -* [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard. -* [Bazil](https://github.com/bazillion/bazil) - A file system that lets your data reside where it is most convenient for it to reside. -* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb. -* [Skybox Analytics](https://github.com/skybox/skybox) - A standalone funnel analysis tool for web analytics. -* [Scuttlebutt](https://github.com/benbjohnson/scuttlebutt) - Uses Bolt to store and process all Twitter mentions of GitHub projects. -* [Wiki](https://github.com/peterhellberg/wiki) - A tiny wiki using Goji, BoltDB and Blackfriday. -* [ChainStore](https://github.com/nulayer/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations. -* [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite. -* [Gitchain](https://github.com/gitchain/gitchain) - Decentralized, peer-to-peer Git repositories aka "Git meets Bitcoin". -* [event-shuttle](https://github.com/sclasen/event-shuttle) - A Unix system service to collect and reliably deliver messages to Kafka. -* [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed. -* [BoltStore](https://github.com/yosssi/boltstore) - Session store using Bolt. -* [photosite/session](http://godoc.org/bitbucket.org/kardianos/photosite/session) - Sessions for a photo viewing site. -* [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage. -* [ipLocator](https://github.com/AndreasBriese/ipLocator) - A fast ip-geo-location-server using bolt with bloom filters. -* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend. -* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend. -* [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server. -* [SkyDB](https://github.com/skydb/sky) - Behavioral analytics database. - -If you are using Bolt in a project please send a pull request to add it to the list. diff --git a/_third_party/github.com/boltdb/bolt/batch.go b/_third_party/github.com/boltdb/bolt/batch.go new file mode 100644 index 0000000..bef1f4a --- /dev/null +++ b/_third_party/github.com/boltdb/bolt/batch.go @@ -0,0 +1,135 @@ +package bolt + +import ( + "errors" + "fmt" + "sync" + "time" +) + +// Batch calls fn as part of a batch. It behaves similar to Update, +// except: +// +// 1. concurrent Batch calls can be combined into a single Bolt +// transaction. +// +// 2. the function passed to Batch may be called multiple times, +// regardless of whether it returns error or not. +// +// This means that Batch function side effects must be idempotent and +// take permanent effect only after a successful return is seen in +// caller. +// +// Batch is only useful when there are multiple goroutines calling it. +func (db *DB) Batch(fn func(*Tx) error) error { + errCh := make(chan error, 1) + + db.batchMu.Lock() + if (db.batch == nil) || (db.batch != nil && len(db.batch.calls) >= db.MaxBatchSize) { + // There is no existing batch, or the existing batch is full; start a new one. + db.batch = &batch{ + db: db, + } + db.batch.timer = time.AfterFunc(db.MaxBatchDelay, db.batch.trigger) + } + db.batch.calls = append(db.batch.calls, call{fn: fn, err: errCh}) + if len(db.batch.calls) >= db.MaxBatchSize { + // wake up batch, it's ready to run + go db.batch.trigger() + } + db.batchMu.Unlock() + + err := <-errCh + if err == trySolo { + err = db.Update(fn) + } + return err +} + +type call struct { + fn func(*Tx) error + err chan<- error +} + +type batch struct { + db *DB + timer *time.Timer + start sync.Once + calls []call +} + +// trigger runs the batch if it hasn't already been run. +func (b *batch) trigger() { + b.start.Do(b.run) +} + +// run performs the transactions in the batch and communicates results +// back to DB.Batch. +func (b *batch) run() { + b.db.batchMu.Lock() + b.timer.Stop() + // Make sure no new work is added to this batch, but don't break + // other batches. + if b.db.batch == b { + b.db.batch = nil + } + b.db.batchMu.Unlock() + +retry: + for len(b.calls) > 0 { + var failIdx = -1 + err := b.db.Update(func(tx *Tx) error { + for i, c := range b.calls { + if err := safelyCall(c.fn, tx); err != nil { + failIdx = i + return err + } + } + return nil + }) + + if failIdx >= 0 { + // take the failing transaction out of the batch. it's + // safe to shorten b.calls here because db.batch no longer + // points to us, and we hold the mutex anyway. + c := b.calls[failIdx] + b.calls[failIdx], b.calls = b.calls[len(b.calls)-1], b.calls[:len(b.calls)-1] + // tell the submitter re-run it solo, continue with the rest of the batch + c.err <- trySolo + continue retry + } + + // pass success, or bolt internal errors, to all callers + for _, c := range b.calls { + if c.err != nil { + c.err <- err + } + } + break retry + } +} + +// trySolo is a special sentinel error value used for signaling that a +// transaction function should be re-run. It should never be seen by +// callers. +var trySolo = errors.New("batch function returned an error and should be re-run solo") + +type panicked struct { + reason interface{} +} + +func (p panicked) Error() string { + if err, ok := p.reason.(error); ok { + return err.Error() + } + return fmt.Sprintf("panic: %v", p.reason) +} + +func safelyCall(fn func(*Tx) error, tx *Tx) (err error) { + defer func() { + if p := recover(); p != nil { + err = panicked{p} + } + }() + return fn(tx) +} diff --git a/_third_party/github.com/boltdb/bolt/bolt_386.go b/_third_party/github.com/boltdb/bolt/bolt_386.go index cc21894..e659bfb 100644 --- a/_third_party/github.com/boltdb/bolt/bolt_386.go +++ b/_third_party/github.com/boltdb/bolt/bolt_386.go @@ -2,3 +2,6 @@ package bolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0x7FFFFFFF // 2GB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0xFFFFFFF diff --git a/_third_party/github.com/boltdb/bolt/bolt_amd64.go b/_third_party/github.com/boltdb/bolt/bolt_amd64.go index 4262932..cca6b7e 100644 --- a/_third_party/github.com/boltdb/bolt/bolt_amd64.go +++ b/_third_party/github.com/boltdb/bolt/bolt_amd64.go @@ -2,3 +2,6 @@ package bolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0xFFFFFFFFFFFF // 256TB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0x7FFFFFFF diff --git a/_third_party/github.com/boltdb/bolt/bolt_arm.go b/_third_party/github.com/boltdb/bolt/bolt_arm.go index cc21894..e659bfb 100644 --- a/_third_party/github.com/boltdb/bolt/bolt_arm.go +++ b/_third_party/github.com/boltdb/bolt/bolt_arm.go @@ -2,3 +2,6 @@ package bolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0x7FFFFFFF // 2GB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0xFFFFFFF diff --git a/_third_party/github.com/boltdb/bolt/bolt_test.go b/_third_party/github.com/boltdb/bolt/bolt_test.go deleted file mode 100644 index b7bea1f..0000000 --- a/_third_party/github.com/boltdb/bolt/bolt_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package bolt_test - -import ( - "fmt" - "path/filepath" - "reflect" - "runtime" - "testing" -) - -// assert fails the test if the condition is false. -func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { - if !condition { - _, file, line, _ := runtime.Caller(1) - fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) - tb.FailNow() - } -} - -// ok fails the test if an err is not nil. -func ok(tb testing.TB, err error) { - if err != nil { - _, file, line, _ := runtime.Caller(1) - fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) - tb.FailNow() - } -} - -// equals fails the test if exp is not equal to act. -func equals(tb testing.TB, exp, act interface{}) { - if !reflect.DeepEqual(exp, act) { - _, file, line, _ := runtime.Caller(1) - fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) - tb.FailNow() - } -} diff --git a/_third_party/github.com/boltdb/bolt/bucket.go b/_third_party/github.com/boltdb/bolt/bucket.go index 470689b..6766992 100644 --- a/_third_party/github.com/boltdb/bolt/bucket.go +++ b/_third_party/github.com/boltdb/bolt/bucket.go @@ -252,6 +252,7 @@ func (b *Bucket) DeleteBucket(key []byte) error { // Get retrieves the value for a key in the bucket. // Returns a nil value if the key does not exist or if the key is a nested bucket. +// The returned value is only valid for the life of the transaction. func (b *Bucket) Get(key []byte) []byte { k, v, flags := b.Cursor().seek(key) diff --git a/_third_party/github.com/boltdb/bolt/bucket_test.go b/_third_party/github.com/boltdb/bolt/bucket_test.go deleted file mode 100644 index 94472c5..0000000 --- a/_third_party/github.com/boltdb/bolt/bucket_test.go +++ /dev/null @@ -1,1153 +0,0 @@ -package bolt_test - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "math/rand" - "os" - "strconv" - "strings" - "testing" - "testing/quick" - - "github.com/mjibson/mog/_third_party/github.com/boltdb/bolt" -) - -// Ensure that a bucket that gets a non-existent key returns nil. -func TestBucket_Get_NonExistent(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) - assert(t, value == nil, "") - return nil - }) -} - -// Ensure that a bucket can read a value that is not flushed yet. -func TestBucket_Get_FromNode(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - b.Put([]byte("foo"), []byte("bar")) - value := b.Get([]byte("foo")) - equals(t, []byte("bar"), value) - return nil - }) -} - -// Ensure that a bucket retrieved via Get() returns a nil. -func TestBucket_Get_IncompatibleValue(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - _, err := tx.Bucket([]byte("widgets")).CreateBucket([]byte("foo")) - ok(t, err) - assert(t, tx.Bucket([]byte("widgets")).Get([]byte("foo")) == nil, "") - return nil - }) -} - -// Ensure that a bucket can write a key/value. -func TestBucket_Put(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - err := tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) - ok(t, err) - value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) - equals(t, value, []byte("bar")) - return nil - }) -} - -// Ensure that a bucket can rewrite a key in the same transaction. -func TestBucket_Put_Repeat(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - ok(t, b.Put([]byte("foo"), []byte("bar"))) - ok(t, b.Put([]byte("foo"), []byte("baz"))) - value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) - equals(t, value, []byte("baz")) - return nil - }) -} - -// Ensure that a bucket can write a bunch of large values. -func TestBucket_Put_Large(t *testing.T) { - db := NewTestDB() - defer db.Close() - - count, factor := 100, 200 - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - for i := 1; i < count; i++ { - ok(t, b.Put([]byte(strings.Repeat("0", i*factor)), []byte(strings.Repeat("X", (count-i)*factor)))) - } - return nil - }) - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("widgets")) - for i := 1; i < count; i++ { - value := b.Get([]byte(strings.Repeat("0", i*factor))) - equals(t, []byte(strings.Repeat("X", (count-i)*factor)), value) - } - return nil - }) -} - -// Ensure that a database can perform multiple large appends safely. -func TestDB_Put_VeryLarge(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - n, batchN := 400000, 200000 - ksize, vsize := 8, 500 - - db := NewTestDB() - defer db.Close() - - for i := 0; i < n; i += batchN { - err := db.Update(func(tx *bolt.Tx) error { - b, _ := tx.CreateBucketIfNotExists([]byte("widgets")) - for j := 0; j < batchN; j++ { - k, v := make([]byte, ksize), make([]byte, vsize) - binary.BigEndian.PutUint32(k, uint32(i+j)) - ok(t, b.Put(k, v)) - } - return nil - }) - ok(t, err) - } -} - -// Ensure that a setting a value on a key with a bucket value returns an error. -func TestBucket_Put_IncompatibleValue(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - _, err := tx.Bucket([]byte("widgets")).CreateBucket([]byte("foo")) - ok(t, err) - equals(t, bolt.ErrIncompatibleValue, tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))) - return nil - }) -} - -// Ensure that a setting a value while the transaction is closed returns an error. -func TestBucket_Put_Closed(t *testing.T) { - db := NewTestDB() - defer db.Close() - tx, _ := db.Begin(true) - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - tx.Rollback() - equals(t, bolt.ErrTxClosed, b.Put([]byte("foo"), []byte("bar"))) -} - -// Ensure that setting a value on a read-only bucket returns an error. -func TestBucket_Put_ReadOnly(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - ok(t, err) - return nil - }) - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("widgets")) - err := b.Put([]byte("foo"), []byte("bar")) - equals(t, err, bolt.ErrTxNotWritable) - return nil - }) -} - -// Ensure that a bucket can delete an existing key. -func TestBucket_Delete(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) - err := tx.Bucket([]byte("widgets")).Delete([]byte("foo")) - ok(t, err) - value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) - assert(t, value == nil, "") - return nil - }) -} - -// Ensure that deleting a large set of keys will work correctly. -func TestBucket_Delete_Large(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - var b, _ = tx.CreateBucket([]byte("widgets")) - for i := 0; i < 100; i++ { - ok(t, b.Put([]byte(strconv.Itoa(i)), []byte(strings.Repeat("*", 1024)))) - } - return nil - }) - db.Update(func(tx *bolt.Tx) error { - var b = tx.Bucket([]byte("widgets")) - for i := 0; i < 100; i++ { - ok(t, b.Delete([]byte(strconv.Itoa(i)))) - } - return nil - }) - db.View(func(tx *bolt.Tx) error { - var b = tx.Bucket([]byte("widgets")) - for i := 0; i < 100; i++ { - assert(t, b.Get([]byte(strconv.Itoa(i))) == nil, "") - } - return nil - }) -} - -// Deleting a very large list of keys will cause the freelist to use overflow. -func TestBucket_Delete_FreelistOverflow(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - db := NewTestDB() - defer db.Close() - k := make([]byte, 16) - for i := uint64(0); i < 10000; i++ { - err := db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucketIfNotExists([]byte("0")) - if err != nil { - t.Fatalf("bucket error: %s", err) - } - - for j := uint64(0); j < 1000; j++ { - binary.BigEndian.PutUint64(k[:8], i) - binary.BigEndian.PutUint64(k[8:], j) - if err := b.Put(k, nil); err != nil { - t.Fatalf("put error: %s", err) - } - } - - return nil - }) - - if err != nil { - t.Fatalf("update error: %s", err) - } - } - - // Delete all of them in one large transaction - err := db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("0")) - c := b.Cursor() - for k, _ := c.First(); k != nil; k, _ = c.Next() { - b.Delete(k) - } - return nil - }) - - // Check that a freelist overflow occurred. - ok(t, err) -} - -// Ensure that accessing and updating nested buckets is ok across transactions. -func TestBucket_Nested(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - // Create a widgets bucket. - b, err := tx.CreateBucket([]byte("widgets")) - ok(t, err) - - // Create a widgets/foo bucket. - _, err = b.CreateBucket([]byte("foo")) - ok(t, err) - - // Create a widgets/bar key. - ok(t, b.Put([]byte("bar"), []byte("0000"))) - - return nil - }) - db.MustCheck() - - // Update widgets/bar. - db.Update(func(tx *bolt.Tx) error { - var b = tx.Bucket([]byte("widgets")) - ok(t, b.Put([]byte("bar"), []byte("xxxx"))) - return nil - }) - db.MustCheck() - - // Cause a split. - db.Update(func(tx *bolt.Tx) error { - var b = tx.Bucket([]byte("widgets")) - for i := 0; i < 10000; i++ { - ok(t, b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i)))) - } - return nil - }) - db.MustCheck() - - // Insert into widgets/foo/baz. - db.Update(func(tx *bolt.Tx) error { - var b = tx.Bucket([]byte("widgets")) - ok(t, b.Bucket([]byte("foo")).Put([]byte("baz"), []byte("yyyy"))) - return nil - }) - db.MustCheck() - - // Verify. - db.View(func(tx *bolt.Tx) error { - var b = tx.Bucket([]byte("widgets")) - equals(t, []byte("yyyy"), b.Bucket([]byte("foo")).Get([]byte("baz"))) - equals(t, []byte("xxxx"), b.Get([]byte("bar"))) - for i := 0; i < 10000; i++ { - equals(t, []byte(strconv.Itoa(i)), b.Get([]byte(strconv.Itoa(i)))) - } - return nil - }) -} - -// Ensure that deleting a bucket using Delete() returns an error. -func TestBucket_Delete_Bucket(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - _, err := b.CreateBucket([]byte("foo")) - ok(t, err) - equals(t, bolt.ErrIncompatibleValue, b.Delete([]byte("foo"))) - return nil - }) -} - -// Ensure that deleting a key on a read-only bucket returns an error. -func TestBucket_Delete_ReadOnly(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - return nil - }) - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("widgets")) - err := b.Delete([]byte("foo")) - equals(t, err, bolt.ErrTxNotWritable) - return nil - }) -} - -// Ensure that a deleting value while the transaction is closed returns an error. -func TestBucket_Delete_Closed(t *testing.T) { - db := NewTestDB() - defer db.Close() - tx, _ := db.Begin(true) - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - tx.Rollback() - equals(t, bolt.ErrTxClosed, b.Delete([]byte("foo"))) -} - -// Ensure that deleting a bucket causes nested buckets to be deleted. -func TestBucket_DeleteBucket_Nested(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - _, err := tx.Bucket([]byte("widgets")).CreateBucket([]byte("foo")) - ok(t, err) - _, err = tx.Bucket([]byte("widgets")).Bucket([]byte("foo")).CreateBucket([]byte("bar")) - ok(t, err) - ok(t, tx.Bucket([]byte("widgets")).Bucket([]byte("foo")).Bucket([]byte("bar")).Put([]byte("baz"), []byte("bat"))) - ok(t, tx.Bucket([]byte("widgets")).DeleteBucket([]byte("foo"))) - return nil - }) -} - -// Ensure that deleting a bucket causes nested buckets to be deleted after they have been committed. -func TestBucket_DeleteBucket_Nested2(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - _, err := tx.Bucket([]byte("widgets")).CreateBucket([]byte("foo")) - ok(t, err) - _, err = tx.Bucket([]byte("widgets")).Bucket([]byte("foo")).CreateBucket([]byte("bar")) - ok(t, err) - ok(t, tx.Bucket([]byte("widgets")).Bucket([]byte("foo")).Bucket([]byte("bar")).Put([]byte("baz"), []byte("bat"))) - return nil - }) - db.Update(func(tx *bolt.Tx) error { - assert(t, tx.Bucket([]byte("widgets")) != nil, "") - assert(t, tx.Bucket([]byte("widgets")).Bucket([]byte("foo")) != nil, "") - assert(t, tx.Bucket([]byte("widgets")).Bucket([]byte("foo")).Bucket([]byte("bar")) != nil, "") - equals(t, []byte("bat"), tx.Bucket([]byte("widgets")).Bucket([]byte("foo")).Bucket([]byte("bar")).Get([]byte("baz"))) - ok(t, tx.DeleteBucket([]byte("widgets"))) - return nil - }) - db.View(func(tx *bolt.Tx) error { - assert(t, tx.Bucket([]byte("widgets")) == nil, "") - return nil - }) -} - -// Ensure that deleting a child bucket with multiple pages causes all pages to get collected. -func TestBucket_DeleteBucket_Large(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - ok(t, err) - _, err = tx.Bucket([]byte("widgets")).CreateBucket([]byte("foo")) - ok(t, err) - b := tx.Bucket([]byte("widgets")).Bucket([]byte("foo")) - for i := 0; i < 1000; i++ { - ok(t, b.Put([]byte(fmt.Sprintf("%d", i)), []byte(fmt.Sprintf("%0100d", i)))) - } - return nil - }) - db.Update(func(tx *bolt.Tx) error { - ok(t, tx.DeleteBucket([]byte("widgets"))) - return nil - }) - - // NOTE: Consistency check in TestDB.Close() will panic if pages not freed properly. -} - -// Ensure that a simple value retrieved via Bucket() returns a nil. -func TestBucket_Bucket_IncompatibleValue(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - ok(t, tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))) - assert(t, tx.Bucket([]byte("widgets")).Bucket([]byte("foo")) == nil, "") - return nil - }) -} - -// Ensure that creating a bucket on an existing non-bucket key returns an error. -func TestBucket_CreateBucket_IncompatibleValue(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - ok(t, err) - ok(t, tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))) - _, err = tx.Bucket([]byte("widgets")).CreateBucket([]byte("foo")) - equals(t, bolt.ErrIncompatibleValue, err) - return nil - }) -} - -// Ensure that deleting a bucket on an existing non-bucket key returns an error. -func TestBucket_DeleteBucket_IncompatibleValue(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - ok(t, err) - ok(t, tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))) - equals(t, bolt.ErrIncompatibleValue, tx.Bucket([]byte("widgets")).DeleteBucket([]byte("foo"))) - return nil - }) -} - -// Ensure that a bucket can return an autoincrementing sequence. -func TestBucket_NextSequence(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.CreateBucket([]byte("woojits")) - - // Make sure sequence increments. - seq, err := tx.Bucket([]byte("widgets")).NextSequence() - ok(t, err) - equals(t, seq, uint64(1)) - seq, err = tx.Bucket([]byte("widgets")).NextSequence() - ok(t, err) - equals(t, seq, uint64(2)) - - // Buckets should be separate. - seq, err = tx.Bucket([]byte("woojits")).NextSequence() - ok(t, err) - equals(t, seq, uint64(1)) - return nil - }) -} - -// Ensure that a bucket will persist an autoincrementing sequence even if its -// the only thing updated on the bucket. -// https://github.com/boltdb/bolt/issues/296 -func TestBucket_NextSequence_Persist(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - _, _ = tx.CreateBucket([]byte("widgets")) - return nil - }) - - db.Update(func(tx *bolt.Tx) error { - _, _ = tx.Bucket([]byte("widgets")).NextSequence() - return nil - }) - - db.Update(func(tx *bolt.Tx) error { - seq, err := tx.Bucket([]byte("widgets")).NextSequence() - if err != nil { - t.Fatalf("unexpected error: %s", err) - } else if seq != 2 { - t.Fatalf("unexpected sequence: %d", seq) - } - return nil - }) -} - -// Ensure that retrieving the next sequence on a read-only bucket returns an error. -func TestBucket_NextSequence_ReadOnly(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - return nil - }) - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("widgets")) - i, err := b.NextSequence() - equals(t, i, uint64(0)) - equals(t, err, bolt.ErrTxNotWritable) - return nil - }) -} - -// Ensure that retrieving the next sequence for a bucket on a closed database return an error. -func TestBucket_NextSequence_Closed(t *testing.T) { - db := NewTestDB() - defer db.Close() - tx, _ := db.Begin(true) - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - tx.Rollback() - _, err := b.NextSequence() - equals(t, bolt.ErrTxClosed, err) -} - -// Ensure a user can loop over all key/value pairs in a bucket. -func TestBucket_ForEach(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("0000")) - tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("0001")) - tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte("0002")) - - var index int - err := tx.Bucket([]byte("widgets")).ForEach(func(k, v []byte) error { - switch index { - case 0: - equals(t, k, []byte("bar")) - equals(t, v, []byte("0002")) - case 1: - equals(t, k, []byte("baz")) - equals(t, v, []byte("0001")) - case 2: - equals(t, k, []byte("foo")) - equals(t, v, []byte("0000")) - } - index++ - return nil - }) - ok(t, err) - equals(t, index, 3) - return nil - }) -} - -// Ensure a database can stop iteration early. -func TestBucket_ForEach_ShortCircuit(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte("0000")) - tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("0000")) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("0000")) - - var index int - err := tx.Bucket([]byte("widgets")).ForEach(func(k, v []byte) error { - index++ - if bytes.Equal(k, []byte("baz")) { - return errors.New("marker") - } - return nil - }) - equals(t, errors.New("marker"), err) - equals(t, 2, index) - return nil - }) -} - -// Ensure that looping over a bucket on a closed database returns an error. -func TestBucket_ForEach_Closed(t *testing.T) { - db := NewTestDB() - defer db.Close() - tx, _ := db.Begin(true) - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - tx.Rollback() - err := b.ForEach(func(k, v []byte) error { return nil }) - equals(t, bolt.ErrTxClosed, err) -} - -// Ensure that an error is returned when inserting with an empty key. -func TestBucket_Put_EmptyKey(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - err := tx.Bucket([]byte("widgets")).Put([]byte(""), []byte("bar")) - equals(t, err, bolt.ErrKeyRequired) - err = tx.Bucket([]byte("widgets")).Put(nil, []byte("bar")) - equals(t, err, bolt.ErrKeyRequired) - return nil - }) -} - -// Ensure that an error is returned when inserting with a key that's too large. -func TestBucket_Put_KeyTooLarge(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - err := tx.Bucket([]byte("widgets")).Put(make([]byte, 32769), []byte("bar")) - equals(t, err, bolt.ErrKeyTooLarge) - return nil - }) -} - -// Ensure a bucket can calculate stats. -func TestBucket_Stats(t *testing.T) { - db := NewTestDB() - defer db.Close() - - // Add bucket with fewer keys but one big value. - big_key := []byte("really-big-value") - for i := 0; i < 500; i++ { - db.Update(func(tx *bolt.Tx) error { - b, _ := tx.CreateBucketIfNotExists([]byte("woojits")) - return b.Put([]byte(fmt.Sprintf("%03d", i)), []byte(strconv.Itoa(i))) - }) - } - db.Update(func(tx *bolt.Tx) error { - b, _ := tx.CreateBucketIfNotExists([]byte("woojits")) - return b.Put(big_key, []byte(strings.Repeat("*", 10000))) - }) - - db.MustCheck() - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("woojits")) - stats := b.Stats() - equals(t, 1, stats.BranchPageN) - equals(t, 0, stats.BranchOverflowN) - equals(t, 7, stats.LeafPageN) - equals(t, 2, stats.LeafOverflowN) - equals(t, 501, stats.KeyN) - equals(t, 2, stats.Depth) - - branchInuse := 16 // branch page header - branchInuse += 7 * 16 // branch elements - branchInuse += 7 * 3 // branch keys (6 3-byte keys) - equals(t, branchInuse, stats.BranchInuse) - - leafInuse := 7 * 16 // leaf page header - leafInuse += 501 * 16 // leaf elements - leafInuse += 500*3 + len(big_key) // leaf keys - leafInuse += 1*10 + 2*90 + 3*400 + 10000 // leaf values - equals(t, leafInuse, stats.LeafInuse) - - if os.Getpagesize() == 4096 { - // Incompatible page size - equals(t, 4096, stats.BranchAlloc) - equals(t, 36864, stats.LeafAlloc) - } - - equals(t, 1, stats.BucketN) - equals(t, 0, stats.InlineBucketN) - equals(t, 0, stats.InlineBucketInuse) - return nil - }) -} - -// Ensure a bucket with random insertion utilizes fill percentage correctly. -func TestBucket_Stats_RandomFill(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } else if os.Getpagesize() != 4096 { - t.Skip("invalid page size for test") - } - - db := NewTestDB() - defer db.Close() - - // Add a set of values in random order. It will be the same random - // order so we can maintain consistency between test runs. - var count int - r := rand.New(rand.NewSource(42)) - for _, i := range r.Perm(1000) { - db.Update(func(tx *bolt.Tx) error { - b, _ := tx.CreateBucketIfNotExists([]byte("woojits")) - b.FillPercent = 0.9 - for _, j := range r.Perm(100) { - index := (j * 10000) + i - b.Put([]byte(fmt.Sprintf("%d000000000000000", index)), []byte("0000000000")) - count++ - } - return nil - }) - } - db.MustCheck() - - db.View(func(tx *bolt.Tx) error { - s := tx.Bucket([]byte("woojits")).Stats() - equals(t, 100000, s.KeyN) - - equals(t, 98, s.BranchPageN) - equals(t, 0, s.BranchOverflowN) - equals(t, 130984, s.BranchInuse) - equals(t, 401408, s.BranchAlloc) - - equals(t, 3412, s.LeafPageN) - equals(t, 0, s.LeafOverflowN) - equals(t, 4742482, s.LeafInuse) - equals(t, 13975552, s.LeafAlloc) - return nil - }) -} - -// Ensure a bucket can calculate stats. -func TestBucket_Stats_Small(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - // Add a bucket that fits on a single root leaf. - b, err := tx.CreateBucket([]byte("whozawhats")) - ok(t, err) - b.Put([]byte("foo"), []byte("bar")) - - return nil - }) - db.MustCheck() - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("whozawhats")) - stats := b.Stats() - equals(t, 0, stats.BranchPageN) - equals(t, 0, stats.BranchOverflowN) - equals(t, 0, stats.LeafPageN) - equals(t, 0, stats.LeafOverflowN) - equals(t, 1, stats.KeyN) - equals(t, 1, stats.Depth) - equals(t, 0, stats.BranchInuse) - equals(t, 0, stats.LeafInuse) - if os.Getpagesize() == 4096 { - // Incompatible page size - equals(t, 0, stats.BranchAlloc) - equals(t, 0, stats.LeafAlloc) - } - equals(t, 1, stats.BucketN) - equals(t, 1, stats.InlineBucketN) - equals(t, 16+16+6, stats.InlineBucketInuse) - return nil - }) -} - -func TestBucket_Stats_EmptyBucket(t *testing.T) { - db := NewTestDB() - defer db.Close() - - db.Update(func(tx *bolt.Tx) error { - // Add a bucket that fits on a single root leaf. - _, err := tx.CreateBucket([]byte("whozawhats")) - ok(t, err) - return nil - }) - db.MustCheck() - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("whozawhats")) - stats := b.Stats() - equals(t, 0, stats.BranchPageN) - equals(t, 0, stats.BranchOverflowN) - equals(t, 0, stats.LeafPageN) - equals(t, 0, stats.LeafOverflowN) - equals(t, 0, stats.KeyN) - equals(t, 1, stats.Depth) - equals(t, 0, stats.BranchInuse) - equals(t, 0, stats.LeafInuse) - if os.Getpagesize() == 4096 { - // Incompatible page size - equals(t, 0, stats.BranchAlloc) - equals(t, 0, stats.LeafAlloc) - } - equals(t, 1, stats.BucketN) - equals(t, 1, stats.InlineBucketN) - equals(t, 16, stats.InlineBucketInuse) - return nil - }) -} - -// Ensure a bucket can calculate stats. -func TestBucket_Stats_Nested(t *testing.T) { - db := NewTestDB() - defer db.Close() - - db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucket([]byte("foo")) - ok(t, err) - for i := 0; i < 100; i++ { - b.Put([]byte(fmt.Sprintf("%02d", i)), []byte(fmt.Sprintf("%02d", i))) - } - bar, err := b.CreateBucket([]byte("bar")) - ok(t, err) - for i := 0; i < 10; i++ { - bar.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))) - } - baz, err := bar.CreateBucket([]byte("baz")) - ok(t, err) - for i := 0; i < 10; i++ { - baz.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))) - } - return nil - }) - - db.MustCheck() - - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("foo")) - stats := b.Stats() - equals(t, 0, stats.BranchPageN) - equals(t, 0, stats.BranchOverflowN) - equals(t, 2, stats.LeafPageN) - equals(t, 0, stats.LeafOverflowN) - equals(t, 122, stats.KeyN) - equals(t, 3, stats.Depth) - equals(t, 0, stats.BranchInuse) - - foo := 16 // foo (pghdr) - foo += 101 * 16 // foo leaf elements - foo += 100*2 + 100*2 // foo leaf key/values - foo += 3 + 16 // foo -> bar key/value - - bar := 16 // bar (pghdr) - bar += 11 * 16 // bar leaf elements - bar += 10 + 10 // bar leaf key/values - bar += 3 + 16 // bar -> baz key/value - - baz := 16 // baz (inline) (pghdr) - baz += 10 * 16 // baz leaf elements - baz += 10 + 10 // baz leaf key/values - - equals(t, foo+bar+baz, stats.LeafInuse) - if os.Getpagesize() == 4096 { - // Incompatible page size - equals(t, 0, stats.BranchAlloc) - equals(t, 8192, stats.LeafAlloc) - } - equals(t, 3, stats.BucketN) - equals(t, 1, stats.InlineBucketN) - equals(t, baz, stats.InlineBucketInuse) - return nil - }) -} - -// Ensure a large bucket can calculate stats. -func TestBucket_Stats_Large(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - db := NewTestDB() - defer db.Close() - - var index int - for i := 0; i < 100; i++ { - db.Update(func(tx *bolt.Tx) error { - // Add bucket with lots of keys. - b, _ := tx.CreateBucketIfNotExists([]byte("widgets")) - for i := 0; i < 1000; i++ { - b.Put([]byte(strconv.Itoa(index)), []byte(strconv.Itoa(index))) - index++ - } - return nil - }) - } - db.MustCheck() - - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("widgets")) - stats := b.Stats() - equals(t, 13, stats.BranchPageN) - equals(t, 0, stats.BranchOverflowN) - equals(t, 1196, stats.LeafPageN) - equals(t, 0, stats.LeafOverflowN) - equals(t, 100000, stats.KeyN) - equals(t, 3, stats.Depth) - equals(t, 25257, stats.BranchInuse) - equals(t, 2596916, stats.LeafInuse) - if os.Getpagesize() == 4096 { - // Incompatible page size - equals(t, 53248, stats.BranchAlloc) - equals(t, 4898816, stats.LeafAlloc) - } - equals(t, 1, stats.BucketN) - equals(t, 0, stats.InlineBucketN) - equals(t, 0, stats.InlineBucketInuse) - return nil - }) -} - -// Ensure that a bucket can write random keys and values across multiple transactions. -func TestBucket_Put_Single(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - index := 0 - f := func(items testdata) bool { - db := NewTestDB() - defer db.Close() - - m := make(map[string][]byte) - - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - return err - }) - for _, item := range items { - db.Update(func(tx *bolt.Tx) error { - if err := tx.Bucket([]byte("widgets")).Put(item.Key, item.Value); err != nil { - panic("put error: " + err.Error()) - } - m[string(item.Key)] = item.Value - return nil - }) - - // Verify all key/values so far. - db.View(func(tx *bolt.Tx) error { - i := 0 - for k, v := range m { - value := tx.Bucket([]byte("widgets")).Get([]byte(k)) - if !bytes.Equal(value, v) { - t.Logf("value mismatch [run %d] (%d of %d):\nkey: %x\ngot: %x\nexp: %x", index, i, len(m), []byte(k), value, v) - db.CopyTempFile() - t.FailNow() - } - i++ - } - return nil - }) - } - - index++ - return true - } - if err := quick.Check(f, qconfig()); err != nil { - t.Error(err) - } -} - -// Ensure that a transaction can insert multiple key/value pairs at once. -func TestBucket_Put_Multiple(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - f := func(items testdata) bool { - db := NewTestDB() - defer db.Close() - // Bulk insert all values. - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - return err - }) - err := db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("widgets")) - for _, item := range items { - ok(t, b.Put(item.Key, item.Value)) - } - return nil - }) - ok(t, err) - - // Verify all items exist. - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("widgets")) - for _, item := range items { - value := b.Get(item.Key) - if !bytes.Equal(item.Value, value) { - db.CopyTempFile() - t.Fatalf("exp=%x; got=%x", item.Value, value) - } - } - return nil - }) - return true - } - if err := quick.Check(f, qconfig()); err != nil { - t.Error(err) - } -} - -// Ensure that a transaction can delete all key/value pairs and return to a single leaf page. -func TestBucket_Delete_Quick(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - f := func(items testdata) bool { - db := NewTestDB() - defer db.Close() - // Bulk insert all values. - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - return err - }) - err := db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("widgets")) - for _, item := range items { - ok(t, b.Put(item.Key, item.Value)) - } - return nil - }) - ok(t, err) - - // Remove items one at a time and check consistency. - for _, item := range items { - err := db.Update(func(tx *bolt.Tx) error { - return tx.Bucket([]byte("widgets")).Delete(item.Key) - }) - ok(t, err) - } - - // Anything before our deletion index should be nil. - db.View(func(tx *bolt.Tx) error { - tx.Bucket([]byte("widgets")).ForEach(func(k, v []byte) error { - t.Fatalf("bucket should be empty; found: %06x", trunc(k, 3)) - return nil - }) - return nil - }) - return true - } - if err := quick.Check(f, qconfig()); err != nil { - t.Error(err) - } -} - -func ExampleBucket_Put() { - // Open the database. - db, _ := bolt.Open(tempfile(), 0666, nil) - defer os.Remove(db.Path()) - defer db.Close() - - // Start a write transaction. - db.Update(func(tx *bolt.Tx) error { - // Create a bucket. - tx.CreateBucket([]byte("widgets")) - - // Set the value "bar" for the key "foo". - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) - return nil - }) - - // Read value back in a different read-only transaction. - db.View(func(tx *bolt.Tx) error { - value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) - fmt.Printf("The value of 'foo' is: %s\n", value) - return nil - }) - - // Output: - // The value of 'foo' is: bar -} - -func ExampleBucket_Delete() { - // Open the database. - db, _ := bolt.Open(tempfile(), 0666, nil) - defer os.Remove(db.Path()) - defer db.Close() - - // Start a write transaction. - db.Update(func(tx *bolt.Tx) error { - // Create a bucket. - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - - // Set the value "bar" for the key "foo". - b.Put([]byte("foo"), []byte("bar")) - - // Retrieve the key back from the database and verify it. - value := b.Get([]byte("foo")) - fmt.Printf("The value of 'foo' was: %s\n", value) - return nil - }) - - // Delete the key in a different write transaction. - db.Update(func(tx *bolt.Tx) error { - return tx.Bucket([]byte("widgets")).Delete([]byte("foo")) - }) - - // Retrieve the key again. - db.View(func(tx *bolt.Tx) error { - value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) - if value == nil { - fmt.Printf("The value of 'foo' is now: nil\n") - } - return nil - }) - - // Output: - // The value of 'foo' was: bar - // The value of 'foo' is now: nil -} - -func ExampleBucket_ForEach() { - // Open the database. - db, _ := bolt.Open(tempfile(), 0666, nil) - defer os.Remove(db.Path()) - defer db.Close() - - // Insert data into a bucket. - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("animals")) - b := tx.Bucket([]byte("animals")) - b.Put([]byte("dog"), []byte("fun")) - b.Put([]byte("cat"), []byte("lame")) - b.Put([]byte("liger"), []byte("awesome")) - - // Iterate over items in sorted key order. - b.ForEach(func(k, v []byte) error { - fmt.Printf("A %s is %s.\n", k, v) - return nil - }) - return nil - }) - - // Output: - // A cat is lame. - // A dog is fun. - // A liger is awesome. -} diff --git a/_third_party/github.com/boltdb/bolt/cursor.go b/_third_party/github.com/boltdb/bolt/cursor.go index 0d8ed16..006c548 100644 --- a/_third_party/github.com/boltdb/bolt/cursor.go +++ b/_third_party/github.com/boltdb/bolt/cursor.go @@ -10,6 +10,8 @@ import ( // Cursors see nested buckets with value == nil. // Cursors can be obtained from a transaction and are valid as long as the transaction is open. // +// Keys and values returned from the cursor are only valid for the life of the transaction. +// // Changing data while traversing with a cursor may cause it to be invalidated // and return unexpected keys and/or values. You must reposition your cursor // after mutating data. @@ -25,6 +27,7 @@ func (c *Cursor) Bucket() *Bucket { // First moves the cursor to the first item in the bucket and returns its key and value. // If the bucket is empty then a nil key and value are returned. +// The returned key and value are only valid for the life of the transaction. func (c *Cursor) First() (key []byte, value []byte) { _assert(c.bucket.tx.db != nil, "tx closed") c.stack = c.stack[:0] @@ -41,6 +44,7 @@ func (c *Cursor) First() (key []byte, value []byte) { // Last moves the cursor to the last item in the bucket and returns its key and value. // If the bucket is empty then a nil key and value are returned. +// The returned key and value are only valid for the life of the transaction. func (c *Cursor) Last() (key []byte, value []byte) { _assert(c.bucket.tx.db != nil, "tx closed") c.stack = c.stack[:0] @@ -58,6 +62,7 @@ func (c *Cursor) Last() (key []byte, value []byte) { // Next moves the cursor to the next item in the bucket and returns its key and value. // If the cursor is at the end of the bucket then a nil key and value are returned. +// The returned key and value are only valid for the life of the transaction. func (c *Cursor) Next() (key []byte, value []byte) { _assert(c.bucket.tx.db != nil, "tx closed") k, v, flags := c.next() @@ -69,6 +74,7 @@ func (c *Cursor) Next() (key []byte, value []byte) { // Prev moves the cursor to the previous item in the bucket and returns its key and value. // If the cursor is at the beginning of the bucket then a nil key and value are returned. +// The returned key and value are only valid for the life of the transaction. func (c *Cursor) Prev() (key []byte, value []byte) { _assert(c.bucket.tx.db != nil, "tx closed") @@ -100,6 +106,7 @@ func (c *Cursor) Prev() (key []byte, value []byte) { // Seek moves the cursor to a given key and returns it. // If the key does not exist then the next key is used. If no keys // follow, a nil key is returned. +// The returned key and value are only valid for the life of the transaction. func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) { k, v, flags := c.seek(seek) diff --git a/_third_party/github.com/boltdb/bolt/cursor_test.go b/_third_party/github.com/boltdb/bolt/cursor_test.go deleted file mode 100644 index c7e5d23..0000000 --- a/_third_party/github.com/boltdb/bolt/cursor_test.go +++ /dev/null @@ -1,511 +0,0 @@ -package bolt_test - -import ( - "bytes" - "encoding/binary" - "fmt" - "os" - "sort" - "testing" - "testing/quick" - - "github.com/mjibson/mog/_third_party/github.com/boltdb/bolt" -) - -// Ensure that a cursor can return a reference to the bucket that created it. -func TestCursor_Bucket(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - b, _ := tx.CreateBucket([]byte("widgets")) - c := b.Cursor() - equals(t, b, c.Bucket()) - return nil - }) -} - -// Ensure that a Tx cursor can seek to the appropriate keys. -func TestCursor_Seek(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucket([]byte("widgets")) - ok(t, err) - ok(t, b.Put([]byte("foo"), []byte("0001"))) - ok(t, b.Put([]byte("bar"), []byte("0002"))) - ok(t, b.Put([]byte("baz"), []byte("0003"))) - _, err = b.CreateBucket([]byte("bkt")) - ok(t, err) - return nil - }) - db.View(func(tx *bolt.Tx) error { - c := tx.Bucket([]byte("widgets")).Cursor() - - // Exact match should go to the key. - k, v := c.Seek([]byte("bar")) - equals(t, []byte("bar"), k) - equals(t, []byte("0002"), v) - - // Inexact match should go to the next key. - k, v = c.Seek([]byte("bas")) - equals(t, []byte("baz"), k) - equals(t, []byte("0003"), v) - - // Low key should go to the first key. - k, v = c.Seek([]byte("")) - equals(t, []byte("bar"), k) - equals(t, []byte("0002"), v) - - // High key should return no key. - k, v = c.Seek([]byte("zzz")) - assert(t, k == nil, "") - assert(t, v == nil, "") - - // Buckets should return their key but no value. - k, v = c.Seek([]byte("bkt")) - equals(t, []byte("bkt"), k) - assert(t, v == nil, "") - - return nil - }) -} - -func TestCursor_Delete(t *testing.T) { - db := NewTestDB() - defer db.Close() - - var count = 1000 - - // Insert every other key between 0 and $count. - db.Update(func(tx *bolt.Tx) error { - b, _ := tx.CreateBucket([]byte("widgets")) - for i := 0; i < count; i += 1 { - k := make([]byte, 8) - binary.BigEndian.PutUint64(k, uint64(i)) - b.Put(k, make([]byte, 100)) - } - b.CreateBucket([]byte("sub")) - return nil - }) - - db.Update(func(tx *bolt.Tx) error { - c := tx.Bucket([]byte("widgets")).Cursor() - bound := make([]byte, 8) - binary.BigEndian.PutUint64(bound, uint64(count/2)) - for key, _ := c.First(); bytes.Compare(key, bound) < 0; key, _ = c.Next() { - if err := c.Delete(); err != nil { - return err - } - } - c.Seek([]byte("sub")) - err := c.Delete() - equals(t, err, bolt.ErrIncompatibleValue) - return nil - }) - - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("widgets")) - equals(t, b.Stats().KeyN, count/2+1) - return nil - }) -} - -// Ensure that a Tx cursor can seek to the appropriate keys when there are a -// large number of keys. This test also checks that seek will always move -// forward to the next key. -// -// Related: https://github.com/boltdb/bolt/pull/187 -func TestCursor_Seek_Large(t *testing.T) { - db := NewTestDB() - defer db.Close() - - var count = 10000 - - // Insert every other key between 0 and $count. - db.Update(func(tx *bolt.Tx) error { - b, _ := tx.CreateBucket([]byte("widgets")) - for i := 0; i < count; i += 100 { - for j := i; j < i+100; j += 2 { - k := make([]byte, 8) - binary.BigEndian.PutUint64(k, uint64(j)) - b.Put(k, make([]byte, 100)) - } - } - return nil - }) - - db.View(func(tx *bolt.Tx) error { - c := tx.Bucket([]byte("widgets")).Cursor() - for i := 0; i < count; i++ { - seek := make([]byte, 8) - binary.BigEndian.PutUint64(seek, uint64(i)) - - k, _ := c.Seek(seek) - - // The last seek is beyond the end of the the range so - // it should return nil. - if i == count-1 { - assert(t, k == nil, "") - continue - } - - // Otherwise we should seek to the exact key or the next key. - num := binary.BigEndian.Uint64(k) - if i%2 == 0 { - equals(t, uint64(i), num) - } else { - equals(t, uint64(i+1), num) - } - } - - return nil - }) -} - -// Ensure that a cursor can iterate over an empty bucket without error. -func TestCursor_EmptyBucket(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - return err - }) - db.View(func(tx *bolt.Tx) error { - c := tx.Bucket([]byte("widgets")).Cursor() - k, v := c.First() - assert(t, k == nil, "") - assert(t, v == nil, "") - return nil - }) -} - -// Ensure that a Tx cursor can reverse iterate over an empty bucket without error. -func TestCursor_EmptyBucketReverse(t *testing.T) { - db := NewTestDB() - defer db.Close() - - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - return err - }) - db.View(func(tx *bolt.Tx) error { - c := tx.Bucket([]byte("widgets")).Cursor() - k, v := c.Last() - assert(t, k == nil, "") - assert(t, v == nil, "") - return nil - }) -} - -// Ensure that a Tx cursor can iterate over a single root with a couple elements. -func TestCursor_Iterate_Leaf(t *testing.T) { - db := NewTestDB() - defer db.Close() - - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte{}) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{0}) - tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{1}) - return nil - }) - tx, _ := db.Begin(false) - c := tx.Bucket([]byte("widgets")).Cursor() - - k, v := c.First() - equals(t, string(k), "bar") - equals(t, v, []byte{1}) - - k, v = c.Next() - equals(t, string(k), "baz") - equals(t, v, []byte{}) - - k, v = c.Next() - equals(t, string(k), "foo") - equals(t, v, []byte{0}) - - k, v = c.Next() - assert(t, k == nil, "") - assert(t, v == nil, "") - - k, v = c.Next() - assert(t, k == nil, "") - assert(t, v == nil, "") - - tx.Rollback() -} - -// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements. -func TestCursor_LeafRootReverse(t *testing.T) { - db := NewTestDB() - defer db.Close() - - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte{}) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{0}) - tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{1}) - return nil - }) - tx, _ := db.Begin(false) - c := tx.Bucket([]byte("widgets")).Cursor() - - k, v := c.Last() - equals(t, string(k), "foo") - equals(t, v, []byte{0}) - - k, v = c.Prev() - equals(t, string(k), "baz") - equals(t, v, []byte{}) - - k, v = c.Prev() - equals(t, string(k), "bar") - equals(t, v, []byte{1}) - - k, v = c.Prev() - assert(t, k == nil, "") - assert(t, v == nil, "") - - k, v = c.Prev() - assert(t, k == nil, "") - assert(t, v == nil, "") - - tx.Rollback() -} - -// Ensure that a Tx cursor can restart from the beginning. -func TestCursor_Restart(t *testing.T) { - db := NewTestDB() - defer db.Close() - - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{}) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{}) - return nil - }) - - tx, _ := db.Begin(false) - c := tx.Bucket([]byte("widgets")).Cursor() - - k, _ := c.First() - equals(t, string(k), "bar") - - k, _ = c.Next() - equals(t, string(k), "foo") - - k, _ = c.First() - equals(t, string(k), "bar") - - k, _ = c.Next() - equals(t, string(k), "foo") - - tx.Rollback() -} - -// Ensure that a Tx can iterate over all elements in a bucket. -func TestCursor_QuickCheck(t *testing.T) { - f := func(items testdata) bool { - db := NewTestDB() - defer db.Close() - - // Bulk insert all values. - tx, _ := db.Begin(true) - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - for _, item := range items { - ok(t, b.Put(item.Key, item.Value)) - } - ok(t, tx.Commit()) - - // Sort test data. - sort.Sort(items) - - // Iterate over all items and check consistency. - var index = 0 - tx, _ = db.Begin(false) - c := tx.Bucket([]byte("widgets")).Cursor() - for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() { - equals(t, k, items[index].Key) - equals(t, v, items[index].Value) - index++ - } - equals(t, len(items), index) - tx.Rollback() - - return true - } - if err := quick.Check(f, qconfig()); err != nil { - t.Error(err) - } -} - -// Ensure that a transaction can iterate over all elements in a bucket in reverse. -func TestCursor_QuickCheck_Reverse(t *testing.T) { - f := func(items testdata) bool { - db := NewTestDB() - defer db.Close() - - // Bulk insert all values. - tx, _ := db.Begin(true) - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - for _, item := range items { - ok(t, b.Put(item.Key, item.Value)) - } - ok(t, tx.Commit()) - - // Sort test data. - sort.Sort(revtestdata(items)) - - // Iterate over all items and check consistency. - var index = 0 - tx, _ = db.Begin(false) - c := tx.Bucket([]byte("widgets")).Cursor() - for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() { - equals(t, k, items[index].Key) - equals(t, v, items[index].Value) - index++ - } - equals(t, len(items), index) - tx.Rollback() - - return true - } - if err := quick.Check(f, qconfig()); err != nil { - t.Error(err) - } -} - -// Ensure that a Tx cursor can iterate over subbuckets. -func TestCursor_QuickCheck_BucketsOnly(t *testing.T) { - db := NewTestDB() - defer db.Close() - - db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucket([]byte("widgets")) - ok(t, err) - _, err = b.CreateBucket([]byte("foo")) - ok(t, err) - _, err = b.CreateBucket([]byte("bar")) - ok(t, err) - _, err = b.CreateBucket([]byte("baz")) - ok(t, err) - return nil - }) - db.View(func(tx *bolt.Tx) error { - var names []string - c := tx.Bucket([]byte("widgets")).Cursor() - for k, v := c.First(); k != nil; k, v = c.Next() { - names = append(names, string(k)) - assert(t, v == nil, "") - } - equals(t, names, []string{"bar", "baz", "foo"}) - return nil - }) -} - -// Ensure that a Tx cursor can reverse iterate over subbuckets. -func TestCursor_QuickCheck_BucketsOnly_Reverse(t *testing.T) { - db := NewTestDB() - defer db.Close() - - db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucket([]byte("widgets")) - ok(t, err) - _, err = b.CreateBucket([]byte("foo")) - ok(t, err) - _, err = b.CreateBucket([]byte("bar")) - ok(t, err) - _, err = b.CreateBucket([]byte("baz")) - ok(t, err) - return nil - }) - db.View(func(tx *bolt.Tx) error { - var names []string - c := tx.Bucket([]byte("widgets")).Cursor() - for k, v := c.Last(); k != nil; k, v = c.Prev() { - names = append(names, string(k)) - assert(t, v == nil, "") - } - equals(t, names, []string{"foo", "baz", "bar"}) - return nil - }) -} - -func ExampleCursor() { - // Open the database. - db, _ := bolt.Open(tempfile(), 0666, nil) - defer os.Remove(db.Path()) - defer db.Close() - - // Start a read-write transaction. - db.Update(func(tx *bolt.Tx) error { - // Create a new bucket. - tx.CreateBucket([]byte("animals")) - - // Insert data into a bucket. - b := tx.Bucket([]byte("animals")) - b.Put([]byte("dog"), []byte("fun")) - b.Put([]byte("cat"), []byte("lame")) - b.Put([]byte("liger"), []byte("awesome")) - - // Create a cursor for iteration. - c := b.Cursor() - - // Iterate over items in sorted key order. This starts from the - // first key/value pair and updates the k/v variables to the - // next key/value on each iteration. - // - // The loop finishes at the end of the cursor when a nil key is returned. - for k, v := c.First(); k != nil; k, v = c.Next() { - fmt.Printf("A %s is %s.\n", k, v) - } - - return nil - }) - - // Output: - // A cat is lame. - // A dog is fun. - // A liger is awesome. -} - -func ExampleCursor_reverse() { - // Open the database. - db, _ := bolt.Open(tempfile(), 0666, nil) - defer os.Remove(db.Path()) - defer db.Close() - - // Start a read-write transaction. - db.Update(func(tx *bolt.Tx) error { - // Create a new bucket. - tx.CreateBucket([]byte("animals")) - - // Insert data into a bucket. - b := tx.Bucket([]byte("animals")) - b.Put([]byte("dog"), []byte("fun")) - b.Put([]byte("cat"), []byte("lame")) - b.Put([]byte("liger"), []byte("awesome")) - - // Create a cursor for iteration. - c := b.Cursor() - - // Iterate over items in reverse sorted key order. This starts - // from the last key/value pair and updates the k/v variables to - // the previous key/value on each iteration. - // - // The loop finishes at the beginning of the cursor when a nil key - // is returned. - for k, v := c.Last(); k != nil; k, v = c.Prev() { - fmt.Printf("A %s is %s.\n", k, v) - } - - return nil - }) - - // Output: - // A liger is awesome. - // A dog is fun. - // A cat is lame. -} diff --git a/_third_party/github.com/boltdb/bolt/db.go b/_third_party/github.com/boltdb/bolt/db.go index 4775850..8f0e90b 100644 --- a/_third_party/github.com/boltdb/bolt/db.go +++ b/_third_party/github.com/boltdb/bolt/db.go @@ -27,6 +27,12 @@ const magic uint32 = 0xED0CDAED // must be synchronzied using the msync(2) syscall. const IgnoreNoSync = runtime.GOOS == "openbsd" +// Default values if not set in a DB instance. +const ( + DefaultMaxBatchSize int = 1000 + DefaultMaxBatchDelay = 10 * time.Millisecond +) + // DB represents a collection of buckets persisted to a file on disk. // All data access is performed through transactions which can be obtained through the DB. // All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called. @@ -49,9 +55,25 @@ type DB struct { // THIS IS UNSAFE. PLEASE USE WITH CAUTION. NoSync bool + // MaxBatchSize is the maximum size of a batch. Default value is + // copied from DefaultMaxBatchSize in Open. + // + // If <=0, disables batching. + // + // Do not change concurrently with calls to Batch. + MaxBatchSize int + + // MaxBatchDelay is the maximum delay before a batch starts. + // Default value is copied from DefaultMaxBatchDelay in Open. + // + // If <=0, effectively disables batching. + // + // Do not change concurrently with calls to Batch. + MaxBatchDelay time.Duration + path string file *os.File - dataref []byte + dataref []byte // mmap'ed readonly, write throws SEGV data *[maxMapSize]byte datasz int meta0 *meta @@ -63,6 +85,9 @@ type DB struct { freelist *freelist stats Stats + batchMu sync.Mutex + batch *batch + rwlock sync.Mutex // Allows only one writer at a time. metalock sync.Mutex // Protects meta page access. mmaplock sync.RWMutex // Protects mmap access during remapping. @@ -99,6 +124,10 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) { options = DefaultOptions } + // Set default values for later DB operations. + db.MaxBatchSize = DefaultMaxBatchSize + db.MaxBatchDelay = DefaultMaxBatchDelay + // Open data file and separate sync handler for metadata writes. db.path = path @@ -215,7 +244,7 @@ func (db *DB) munmap() error { } // mmapSize determines the appropriate size for the mmap given the current size -// of the database. The minimum size is 4MB and doubles until it reaches 1GB. +// of the database. The minimum size is 1MB and doubles until it reaches 1GB. // Returns an error if the new mmap size is greater than the max allowed. func (db *DB) mmapSize(size int) (int, error) { // Double the size from 1MB until 1GB. diff --git a/_third_party/github.com/boltdb/bolt/db_test.go b/_third_party/github.com/boltdb/bolt/db_test.go deleted file mode 100644 index 6bab2af..0000000 --- a/_third_party/github.com/boltdb/bolt/db_test.go +++ /dev/null @@ -1,762 +0,0 @@ -package bolt_test - -import ( - "encoding/binary" - "errors" - "flag" - "fmt" - "io/ioutil" - "os" - "regexp" - "runtime" - "sort" - "strings" - "testing" - "time" - - "github.com/mjibson/mog/_third_party/github.com/boltdb/bolt" -) - -var statsFlag = flag.Bool("stats", false, "show performance stats") - -// Ensure that opening a database with a bad path returns an error. -func TestOpen_BadPath(t *testing.T) { - db, err := bolt.Open("", 0666, nil) - assert(t, err != nil, "err: %s", err) - assert(t, db == nil, "") -} - -// Ensure that a database can be opened without error. -func TestOpen(t *testing.T) { - path := tempfile() - defer os.Remove(path) - db, err := bolt.Open(path, 0666, nil) - assert(t, db != nil, "") - ok(t, err) - equals(t, db.Path(), path) - ok(t, db.Close()) -} - -// Ensure that opening an already open database file will timeout. -func TestOpen_Timeout(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("timeout not supported on windows") - } - - path := tempfile() - defer os.Remove(path) - - // Open a data file. - db0, err := bolt.Open(path, 0666, nil) - assert(t, db0 != nil, "") - ok(t, err) - - // Attempt to open the database again. - start := time.Now() - db1, err := bolt.Open(path, 0666, &bolt.Options{Timeout: 100 * time.Millisecond}) - assert(t, db1 == nil, "") - equals(t, bolt.ErrTimeout, err) - assert(t, time.Since(start) > 100*time.Millisecond, "") - - db0.Close() -} - -// Ensure that opening an already open database file will wait until its closed. -func TestOpen_Wait(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("timeout not supported on windows") - } - - path := tempfile() - defer os.Remove(path) - - // Open a data file. - db0, err := bolt.Open(path, 0666, nil) - assert(t, db0 != nil, "") - ok(t, err) - - // Close it in just a bit. - time.AfterFunc(100*time.Millisecond, func() { db0.Close() }) - - // Attempt to open the database again. - start := time.Now() - db1, err := bolt.Open(path, 0666, &bolt.Options{Timeout: 200 * time.Millisecond}) - assert(t, db1 != nil, "") - ok(t, err) - assert(t, time.Since(start) > 100*time.Millisecond, "") -} - -// Ensure that opening a database does not increase its size. -// https://github.com/boltdb/bolt/issues/291 -func TestOpen_Size(t *testing.T) { - // Open a data file. - db := NewTestDB() - path := db.Path() - defer db.Close() - - // Insert until we get above the minimum 4MB size. - ok(t, db.Update(func(tx *bolt.Tx) error { - b, _ := tx.CreateBucketIfNotExists([]byte("data")) - for i := 0; i < 10000; i++ { - ok(t, b.Put([]byte(fmt.Sprintf("%04d", i)), make([]byte, 1000))) - } - return nil - })) - - // Close database and grab the size. - db.DB.Close() - sz := fileSize(path) - if sz == 0 { - t.Fatalf("unexpected new file size: %d", sz) - } - - // Reopen database, update, and check size again. - db0, err := bolt.Open(path, 0666, nil) - ok(t, err) - ok(t, db0.Update(func(tx *bolt.Tx) error { return tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}) })) - ok(t, db0.Close()) - newSz := fileSize(path) - if newSz == 0 { - t.Fatalf("unexpected new file size: %d", newSz) - } - - // Compare the original size with the new size. - if sz != newSz { - t.Fatalf("unexpected file growth: %d => %d", sz, newSz) - } -} - -// Ensure that opening a database beyond the max step size does not increase its size. -// https://github.com/boltdb/bolt/issues/303 -func TestOpen_Size_Large(t *testing.T) { - if testing.Short() { - t.Skip("short mode") - } - - // Open a data file. - db := NewTestDB() - path := db.Path() - defer db.Close() - - // Insert until we get above the minimum 4MB size. - var index uint64 - for i := 0; i < 10000; i++ { - ok(t, db.Update(func(tx *bolt.Tx) error { - b, _ := tx.CreateBucketIfNotExists([]byte("data")) - for j := 0; j < 1000; j++ { - ok(t, b.Put(u64tob(index), make([]byte, 50))) - index++ - } - return nil - })) - } - - // Close database and grab the size. - db.DB.Close() - sz := fileSize(path) - if sz == 0 { - t.Fatalf("unexpected new file size: %d", sz) - } else if sz < (1 << 30) { - t.Fatalf("expected larger initial size: %d", sz) - } - - // Reopen database, update, and check size again. - db0, err := bolt.Open(path, 0666, nil) - ok(t, err) - ok(t, db0.Update(func(tx *bolt.Tx) error { return tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}) })) - ok(t, db0.Close()) - newSz := fileSize(path) - if newSz == 0 { - t.Fatalf("unexpected new file size: %d", newSz) - } - - // Compare the original size with the new size. - if sz != newSz { - t.Fatalf("unexpected file growth: %d => %d", sz, newSz) - } -} - -// Ensure that a re-opened database is consistent. -func TestOpen_Check(t *testing.T) { - path := tempfile() - defer os.Remove(path) - - db, err := bolt.Open(path, 0666, nil) - ok(t, err) - ok(t, db.View(func(tx *bolt.Tx) error { return <-tx.Check() })) - db.Close() - - db, err = bolt.Open(path, 0666, nil) - ok(t, err) - ok(t, db.View(func(tx *bolt.Tx) error { return <-tx.Check() })) - db.Close() -} - -// Ensure that the database returns an error if the file handle cannot be open. -func TestDB_Open_FileError(t *testing.T) { - path := tempfile() - defer os.Remove(path) - - _, err := bolt.Open(path+"/youre-not-my-real-parent", 0666, nil) - assert(t, err.(*os.PathError) != nil, "") - equals(t, path+"/youre-not-my-real-parent", err.(*os.PathError).Path) - equals(t, "open", err.(*os.PathError).Op) -} - -// Ensure that write errors to the meta file handler during initialization are returned. -func TestDB_Open_MetaInitWriteError(t *testing.T) { - t.Skip("pending") -} - -// Ensure that a database that is too small returns an error. -func TestDB_Open_FileTooSmall(t *testing.T) { - path := tempfile() - defer os.Remove(path) - - db, err := bolt.Open(path, 0666, nil) - ok(t, err) - db.Close() - - // corrupt the database - ok(t, os.Truncate(path, int64(os.Getpagesize()))) - - db, err = bolt.Open(path, 0666, nil) - equals(t, errors.New("file size too small"), err) -} - -// TODO(benbjohnson): Test corruption at every byte of the first two pages. - -// Ensure that a database cannot open a transaction when it's not open. -func TestDB_Begin_DatabaseNotOpen(t *testing.T) { - var db bolt.DB - tx, err := db.Begin(false) - assert(t, tx == nil, "") - equals(t, err, bolt.ErrDatabaseNotOpen) -} - -// Ensure that a read-write transaction can be retrieved. -func TestDB_BeginRW(t *testing.T) { - db := NewTestDB() - defer db.Close() - tx, err := db.Begin(true) - assert(t, tx != nil, "") - ok(t, err) - assert(t, tx.DB() == db.DB, "") - equals(t, tx.Writable(), true) - ok(t, tx.Commit()) -} - -// Ensure that opening a transaction while the DB is closed returns an error. -func TestDB_BeginRW_Closed(t *testing.T) { - var db bolt.DB - tx, err := db.Begin(true) - equals(t, err, bolt.ErrDatabaseNotOpen) - assert(t, tx == nil, "") -} - -// Ensure a database can provide a transactional block. -func TestDB_Update(t *testing.T) { - db := NewTestDB() - defer db.Close() - err := db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - b.Put([]byte("foo"), []byte("bar")) - b.Put([]byte("baz"), []byte("bat")) - b.Delete([]byte("foo")) - return nil - }) - ok(t, err) - err = db.View(func(tx *bolt.Tx) error { - assert(t, tx.Bucket([]byte("widgets")).Get([]byte("foo")) == nil, "") - equals(t, []byte("bat"), tx.Bucket([]byte("widgets")).Get([]byte("baz"))) - return nil - }) - ok(t, err) -} - -// Ensure a closed database returns an error while running a transaction block -func TestDB_Update_Closed(t *testing.T) { - var db bolt.DB - err := db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - return nil - }) - equals(t, err, bolt.ErrDatabaseNotOpen) -} - -// Ensure a panic occurs while trying to commit a managed transaction. -func TestDB_Update_ManualCommit(t *testing.T) { - db := NewTestDB() - defer db.Close() - - var ok bool - db.Update(func(tx *bolt.Tx) error { - func() { - defer func() { - if r := recover(); r != nil { - ok = true - } - }() - tx.Commit() - }() - return nil - }) - assert(t, ok, "expected panic") -} - -// Ensure a panic occurs while trying to rollback a managed transaction. -func TestDB_Update_ManualRollback(t *testing.T) { - db := NewTestDB() - defer db.Close() - - var ok bool - db.Update(func(tx *bolt.Tx) error { - func() { - defer func() { - if r := recover(); r != nil { - ok = true - } - }() - tx.Rollback() - }() - return nil - }) - assert(t, ok, "expected panic") -} - -// Ensure a panic occurs while trying to commit a managed transaction. -func TestDB_View_ManualCommit(t *testing.T) { - db := NewTestDB() - defer db.Close() - - var ok bool - db.Update(func(tx *bolt.Tx) error { - func() { - defer func() { - if r := recover(); r != nil { - ok = true - } - }() - tx.Commit() - }() - return nil - }) - assert(t, ok, "expected panic") -} - -// Ensure a panic occurs while trying to rollback a managed transaction. -func TestDB_View_ManualRollback(t *testing.T) { - db := NewTestDB() - defer db.Close() - - var ok bool - db.Update(func(tx *bolt.Tx) error { - func() { - defer func() { - if r := recover(); r != nil { - ok = true - } - }() - tx.Rollback() - }() - return nil - }) - assert(t, ok, "expected panic") -} - -// Ensure a write transaction that panics does not hold open locks. -func TestDB_Update_Panic(t *testing.T) { - db := NewTestDB() - defer db.Close() - - func() { - defer func() { - if r := recover(); r != nil { - t.Log("recover: update", r) - } - }() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - panic("omg") - }) - }() - - // Verify we can update again. - err := db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - return err - }) - ok(t, err) - - // Verify that our change persisted. - err = db.Update(func(tx *bolt.Tx) error { - assert(t, tx.Bucket([]byte("widgets")) != nil, "") - return nil - }) -} - -// Ensure a database can return an error through a read-only transactional block. -func TestDB_View_Error(t *testing.T) { - db := NewTestDB() - defer db.Close() - err := db.View(func(tx *bolt.Tx) error { - return errors.New("xxx") - }) - equals(t, errors.New("xxx"), err) -} - -// Ensure a read transaction that panics does not hold open locks. -func TestDB_View_Panic(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - return nil - }) - - func() { - defer func() { - if r := recover(); r != nil { - t.Log("recover: view", r) - } - }() - db.View(func(tx *bolt.Tx) error { - assert(t, tx.Bucket([]byte("widgets")) != nil, "") - panic("omg") - }) - }() - - // Verify that we can still use read transactions. - db.View(func(tx *bolt.Tx) error { - assert(t, tx.Bucket([]byte("widgets")) != nil, "") - return nil - }) -} - -// Ensure that an error is returned when a database write fails. -func TestDB_Commit_WriteFail(t *testing.T) { - t.Skip("pending") // TODO(benbjohnson) -} - -// Ensure that DB stats can be returned. -func TestDB_Stats(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - return err - }) - stats := db.Stats() - equals(t, 2, stats.TxStats.PageCount) - equals(t, 0, stats.FreePageN) - equals(t, 2, stats.PendingPageN) -} - -// Ensure that database pages are in expected order and type. -func TestDB_Consistency(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - return err - }) - - for i := 0; i < 10; i++ { - db.Update(func(tx *bolt.Tx) error { - ok(t, tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))) - return nil - }) - } - db.Update(func(tx *bolt.Tx) error { - p, _ := tx.Page(0) - assert(t, p != nil, "") - equals(t, "meta", p.Type) - - p, _ = tx.Page(1) - assert(t, p != nil, "") - equals(t, "meta", p.Type) - - p, _ = tx.Page(2) - assert(t, p != nil, "") - equals(t, "free", p.Type) - - p, _ = tx.Page(3) - assert(t, p != nil, "") - equals(t, "free", p.Type) - - p, _ = tx.Page(4) - assert(t, p != nil, "") - equals(t, "leaf", p.Type) - - p, _ = tx.Page(5) - assert(t, p != nil, "") - equals(t, "freelist", p.Type) - - p, _ = tx.Page(6) - assert(t, p == nil, "") - return nil - }) -} - -// Ensure that DB stats can be substracted from one another. -func TestDBStats_Sub(t *testing.T) { - var a, b bolt.Stats - a.TxStats.PageCount = 3 - a.FreePageN = 4 - b.TxStats.PageCount = 10 - b.FreePageN = 14 - diff := b.Sub(&a) - equals(t, 7, diff.TxStats.PageCount) - // free page stats are copied from the receiver and not subtracted - equals(t, 14, diff.FreePageN) -} - -func ExampleDB_Update() { - // Open the database. - db, _ := bolt.Open(tempfile(), 0666, nil) - defer os.Remove(db.Path()) - defer db.Close() - - // Execute several commands within a write transaction. - err := db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucket([]byte("widgets")) - if err != nil { - return err - } - if err := b.Put([]byte("foo"), []byte("bar")); err != nil { - return err - } - return nil - }) - - // If our transactional block didn't return an error then our data is saved. - if err == nil { - db.View(func(tx *bolt.Tx) error { - value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) - fmt.Printf("The value of 'foo' is: %s\n", value) - return nil - }) - } - - // Output: - // The value of 'foo' is: bar -} - -func ExampleDB_View() { - // Open the database. - db, _ := bolt.Open(tempfile(), 0666, nil) - defer os.Remove(db.Path()) - defer db.Close() - - // Insert data into a bucket. - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("people")) - b := tx.Bucket([]byte("people")) - b.Put([]byte("john"), []byte("doe")) - b.Put([]byte("susy"), []byte("que")) - return nil - }) - - // Access data from within a read-only transactional block. - db.View(func(tx *bolt.Tx) error { - v := tx.Bucket([]byte("people")).Get([]byte("john")) - fmt.Printf("John's last name is %s.\n", v) - return nil - }) - - // Output: - // John's last name is doe. -} - -func ExampleDB_Begin_ReadOnly() { - // Open the database. - db, _ := bolt.Open(tempfile(), 0666, nil) - defer os.Remove(db.Path()) - defer db.Close() - - // Create a bucket. - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - return err - }) - - // Create several keys in a transaction. - tx, _ := db.Begin(true) - b := tx.Bucket([]byte("widgets")) - b.Put([]byte("john"), []byte("blue")) - b.Put([]byte("abby"), []byte("red")) - b.Put([]byte("zephyr"), []byte("purple")) - tx.Commit() - - // Iterate over the values in sorted key order. - tx, _ = db.Begin(false) - c := tx.Bucket([]byte("widgets")).Cursor() - for k, v := c.First(); k != nil; k, v = c.Next() { - fmt.Printf("%s likes %s\n", k, v) - } - tx.Rollback() - - // Output: - // abby likes red - // john likes blue - // zephyr likes purple -} - -// TestDB represents a wrapper around a Bolt DB to handle temporary file -// creation and automatic cleanup on close. -type TestDB struct { - *bolt.DB -} - -// NewTestDB returns a new instance of TestDB. -func NewTestDB() *TestDB { - db, err := bolt.Open(tempfile(), 0666, nil) - if err != nil { - panic("cannot open db: " + err.Error()) - } - return &TestDB{db} -} - -// Close closes the database and deletes the underlying file. -func (db *TestDB) Close() { - // Log statistics. - if *statsFlag { - db.PrintStats() - } - - // Check database consistency after every test. - db.MustCheck() - - // Close database and remove file. - defer os.Remove(db.Path()) - db.DB.Close() -} - -// PrintStats prints the database stats -func (db *TestDB) PrintStats() { - var stats = db.Stats() - fmt.Printf("[db] %-20s %-20s %-20s\n", - fmt.Sprintf("pg(%d/%d)", stats.TxStats.PageCount, stats.TxStats.PageAlloc), - fmt.Sprintf("cur(%d)", stats.TxStats.CursorCount), - fmt.Sprintf("node(%d/%d)", stats.TxStats.NodeCount, stats.TxStats.NodeDeref), - ) - fmt.Printf(" %-20s %-20s %-20s\n", - fmt.Sprintf("rebal(%d/%v)", stats.TxStats.Rebalance, truncDuration(stats.TxStats.RebalanceTime)), - fmt.Sprintf("spill(%d/%v)", stats.TxStats.Spill, truncDuration(stats.TxStats.SpillTime)), - fmt.Sprintf("w(%d/%v)", stats.TxStats.Write, truncDuration(stats.TxStats.WriteTime)), - ) -} - -// MustCheck runs a consistency check on the database and panics if any errors are found. -func (db *TestDB) MustCheck() { - db.View(func(tx *bolt.Tx) error { - // Collect all the errors. - var errors []error - for err := range tx.Check() { - errors = append(errors, err) - if len(errors) > 10 { - break - } - } - - // If errors occurred, copy the DB and print the errors. - if len(errors) > 0 { - var path = tempfile() - tx.CopyFile(path, 0600) - - // Print errors. - fmt.Print("\n\n") - fmt.Printf("consistency check failed (%d errors)\n", len(errors)) - for _, err := range errors { - fmt.Println(err) - } - fmt.Println("") - fmt.Println("db saved to:") - fmt.Println(path) - fmt.Print("\n\n") - os.Exit(-1) - } - - return nil - }) -} - -// CopyTempFile copies a database to a temporary file. -func (db *TestDB) CopyTempFile() { - path := tempfile() - db.View(func(tx *bolt.Tx) error { return tx.CopyFile(path, 0600) }) - fmt.Println("db copied to: ", path) -} - -// tempfile returns a temporary file path. -func tempfile() string { - f, _ := ioutil.TempFile("", "bolt-") - f.Close() - os.Remove(f.Name()) - return f.Name() -} - -// mustContainKeys checks that a bucket contains a given set of keys. -func mustContainKeys(b *bolt.Bucket, m map[string]string) { - found := make(map[string]string) - b.ForEach(func(k, _ []byte) error { - found[string(k)] = "" - return nil - }) - - // Check for keys found in bucket that shouldn't be there. - var keys []string - for k, _ := range found { - if _, ok := m[string(k)]; !ok { - keys = append(keys, k) - } - } - if len(keys) > 0 { - sort.Strings(keys) - panic(fmt.Sprintf("keys found(%d): %s", len(keys), strings.Join(keys, ","))) - } - - // Check for keys not found in bucket that should be there. - for k, _ := range m { - if _, ok := found[string(k)]; !ok { - keys = append(keys, k) - } - } - if len(keys) > 0 { - sort.Strings(keys) - panic(fmt.Sprintf("keys not found(%d): %s", len(keys), strings.Join(keys, ","))) - } -} - -func trunc(b []byte, length int) []byte { - if length < len(b) { - return b[:length] - } - return b -} - -func truncDuration(d time.Duration) string { - return regexp.MustCompile(`^(\d+)(\.\d+)`).ReplaceAllString(d.String(), "$1") -} - -func fileSize(path string) int64 { - fi, err := os.Stat(path) - if err != nil { - return 0 - } - return fi.Size() -} - -func warn(v ...interface{}) { fmt.Fprintln(os.Stderr, v...) } -func warnf(msg string, v ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", v...) } - -// u64tob converts a uint64 into an 8-byte slice. -func u64tob(v uint64) []byte { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, v) - return b -} - -// btou64 converts an 8-byte slice into an uint64. -func btou64(b []byte) uint64 { return binary.BigEndian.Uint64(b) } diff --git a/_third_party/github.com/boltdb/bolt/freelist_test.go b/_third_party/github.com/boltdb/bolt/freelist_test.go deleted file mode 100644 index 792ca92..0000000 --- a/_third_party/github.com/boltdb/bolt/freelist_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package bolt - -import ( - "reflect" - "testing" - "unsafe" -) - -// Ensure that a page is added to a transaction's freelist. -func TestFreelist_free(t *testing.T) { - f := newFreelist() - f.free(100, &page{id: 12}) - if !reflect.DeepEqual([]pgid{12}, f.pending[100]) { - t.Fatalf("exp=%v; got=%v", []pgid{12}, f.pending[100]) - } -} - -// Ensure that a page and its overflow is added to a transaction's freelist. -func TestFreelist_free_overflow(t *testing.T) { - f := newFreelist() - f.free(100, &page{id: 12, overflow: 3}) - if exp := []pgid{12, 13, 14, 15}; !reflect.DeepEqual(exp, f.pending[100]) { - t.Fatalf("exp=%v; got=%v", exp, f.pending[100]) - } -} - -// Ensure that a transaction's free pages can be released. -func TestFreelist_release(t *testing.T) { - f := newFreelist() - f.free(100, &page{id: 12, overflow: 1}) - f.free(100, &page{id: 9}) - f.free(102, &page{id: 39}) - f.release(100) - f.release(101) - if exp := []pgid{9, 12, 13}; !reflect.DeepEqual(exp, f.ids) { - t.Fatalf("exp=%v; got=%v", exp, f.ids) - } - - f.release(102) - if exp := []pgid{9, 12, 13, 39}; !reflect.DeepEqual(exp, f.ids) { - t.Fatalf("exp=%v; got=%v", exp, f.ids) - } -} - -// Ensure that a freelist can find contiguous blocks of pages. -func TestFreelist_allocate(t *testing.T) { - f := &freelist{ids: []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18}} - if id := int(f.allocate(3)); id != 3 { - t.Fatalf("exp=3; got=%v", id) - } - if id := int(f.allocate(1)); id != 6 { - t.Fatalf("exp=6; got=%v", id) - } - if id := int(f.allocate(3)); id != 0 { - t.Fatalf("exp=0; got=%v", id) - } - if id := int(f.allocate(2)); id != 12 { - t.Fatalf("exp=12; got=%v", id) - } - if id := int(f.allocate(1)); id != 7 { - t.Fatalf("exp=7; got=%v", id) - } - if id := int(f.allocate(0)); id != 0 { - t.Fatalf("exp=0; got=%v", id) - } - if id := int(f.allocate(0)); id != 0 { - t.Fatalf("exp=0; got=%v", id) - } - if exp := []pgid{9, 18}; !reflect.DeepEqual(exp, f.ids) { - t.Fatalf("exp=%v; got=%v", exp, f.ids) - } - - if id := int(f.allocate(1)); id != 9 { - t.Fatalf("exp=9; got=%v", id) - } - if id := int(f.allocate(1)); id != 18 { - t.Fatalf("exp=18; got=%v", id) - } - if id := int(f.allocate(1)); id != 0 { - t.Fatalf("exp=0; got=%v", id) - } - if exp := []pgid{}; !reflect.DeepEqual(exp, f.ids) { - t.Fatalf("exp=%v; got=%v", exp, f.ids) - } -} - -// Ensure that a freelist can deserialize from a freelist page. -func TestFreelist_read(t *testing.T) { - // Create a page. - var buf [4096]byte - page := (*page)(unsafe.Pointer(&buf[0])) - page.flags = freelistPageFlag - page.count = 2 - - // Insert 2 page ids. - ids := (*[3]pgid)(unsafe.Pointer(&page.ptr)) - ids[0] = 23 - ids[1] = 50 - - // Deserialize page into a freelist. - f := newFreelist() - f.read(page) - - // Ensure that there are two page ids in the freelist. - if exp := []pgid{23, 50}; !reflect.DeepEqual(exp, f.ids) { - t.Fatalf("exp=%v; got=%v", exp, f.ids) - } -} - -// Ensure that a freelist can serialize into a freelist page. -func TestFreelist_write(t *testing.T) { - // Create a freelist and write it to a page. - var buf [4096]byte - f := &freelist{ids: []pgid{12, 39}, pending: make(map[txid][]pgid)} - f.pending[100] = []pgid{28, 11} - f.pending[101] = []pgid{3} - p := (*page)(unsafe.Pointer(&buf[0])) - f.write(p) - - // Read the page back out. - f2 := newFreelist() - f2.read(p) - - // Ensure that the freelist is correct. - // All pages should be present and in reverse order. - if exp := []pgid{3, 11, 12, 28, 39}; !reflect.DeepEqual(exp, f2.ids) { - t.Fatalf("exp=%v; got=%v", exp, f2.ids) - } -} diff --git a/_third_party/github.com/boltdb/bolt/node_test.go b/_third_party/github.com/boltdb/bolt/node_test.go deleted file mode 100644 index fa5d10f..0000000 --- a/_third_party/github.com/boltdb/bolt/node_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package bolt - -import ( - "testing" - "unsafe" -) - -// Ensure that a node can insert a key/value. -func TestNode_put(t *testing.T) { - n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{meta: &meta{pgid: 1}}}} - n.put([]byte("baz"), []byte("baz"), []byte("2"), 0, 0) - n.put([]byte("foo"), []byte("foo"), []byte("0"), 0, 0) - n.put([]byte("bar"), []byte("bar"), []byte("1"), 0, 0) - n.put([]byte("foo"), []byte("foo"), []byte("3"), 0, leafPageFlag) - - if len(n.inodes) != 3 { - t.Fatalf("exp=3; got=%d", len(n.inodes)) - } - if k, v := n.inodes[0].key, n.inodes[0].value; string(k) != "bar" || string(v) != "1" { - t.Fatalf("exp=; got=<%s,%s>", k, v) - } - if k, v := n.inodes[1].key, n.inodes[1].value; string(k) != "baz" || string(v) != "2" { - t.Fatalf("exp=; got=<%s,%s>", k, v) - } - if k, v := n.inodes[2].key, n.inodes[2].value; string(k) != "foo" || string(v) != "3" { - t.Fatalf("exp=; got=<%s,%s>", k, v) - } - if n.inodes[2].flags != uint32(leafPageFlag) { - t.Fatalf("not a leaf: %d", n.inodes[2].flags) - } -} - -// Ensure that a node can deserialize from a leaf page. -func TestNode_read_LeafPage(t *testing.T) { - // Create a page. - var buf [4096]byte - page := (*page)(unsafe.Pointer(&buf[0])) - page.flags = leafPageFlag - page.count = 2 - - // Insert 2 elements at the beginning. sizeof(leafPageElement) == 16 - nodes := (*[3]leafPageElement)(unsafe.Pointer(&page.ptr)) - nodes[0] = leafPageElement{flags: 0, pos: 32, ksize: 3, vsize: 4} // pos = sizeof(leafPageElement) * 2 - nodes[1] = leafPageElement{flags: 0, pos: 23, ksize: 10, vsize: 3} // pos = sizeof(leafPageElement) + 3 + 4 - - // Write data for the nodes at the end. - data := (*[4096]byte)(unsafe.Pointer(&nodes[2])) - copy(data[:], []byte("barfooz")) - copy(data[7:], []byte("helloworldbye")) - - // Deserialize page into a leaf. - n := &node{} - n.read(page) - - // Check that there are two inodes with correct data. - if !n.isLeaf { - t.Fatal("expected leaf") - } - if len(n.inodes) != 2 { - t.Fatalf("exp=2; got=%d", len(n.inodes)) - } - if k, v := n.inodes[0].key, n.inodes[0].value; string(k) != "bar" || string(v) != "fooz" { - t.Fatalf("exp=; got=<%s,%s>", k, v) - } - if k, v := n.inodes[1].key, n.inodes[1].value; string(k) != "helloworld" || string(v) != "bye" { - t.Fatalf("exp=; got=<%s,%s>", k, v) - } -} - -// Ensure that a node can serialize into a leaf page. -func TestNode_write_LeafPage(t *testing.T) { - // Create a node. - n := &node{isLeaf: true, inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}} - n.put([]byte("susy"), []byte("susy"), []byte("que"), 0, 0) - n.put([]byte("ricki"), []byte("ricki"), []byte("lake"), 0, 0) - n.put([]byte("john"), []byte("john"), []byte("johnson"), 0, 0) - - // Write it to a page. - var buf [4096]byte - p := (*page)(unsafe.Pointer(&buf[0])) - n.write(p) - - // Read the page back in. - n2 := &node{} - n2.read(p) - - // Check that the two pages are the same. - if len(n2.inodes) != 3 { - t.Fatalf("exp=3; got=%d", len(n2.inodes)) - } - if k, v := n2.inodes[0].key, n2.inodes[0].value; string(k) != "john" || string(v) != "johnson" { - t.Fatalf("exp=; got=<%s,%s>", k, v) - } - if k, v := n2.inodes[1].key, n2.inodes[1].value; string(k) != "ricki" || string(v) != "lake" { - t.Fatalf("exp=; got=<%s,%s>", k, v) - } - if k, v := n2.inodes[2].key, n2.inodes[2].value; string(k) != "susy" || string(v) != "que" { - t.Fatalf("exp=; got=<%s,%s>", k, v) - } -} - -// Ensure that a node can split into appropriate subgroups. -func TestNode_split(t *testing.T) { - // Create a node. - n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}} - n.put([]byte("00000001"), []byte("00000001"), []byte("0123456701234567"), 0, 0) - n.put([]byte("00000002"), []byte("00000002"), []byte("0123456701234567"), 0, 0) - n.put([]byte("00000003"), []byte("00000003"), []byte("0123456701234567"), 0, 0) - n.put([]byte("00000004"), []byte("00000004"), []byte("0123456701234567"), 0, 0) - n.put([]byte("00000005"), []byte("00000005"), []byte("0123456701234567"), 0, 0) - - // Split between 2 & 3. - n.split(100) - - var parent = n.parent - if len(parent.children) != 2 { - t.Fatalf("exp=2; got=%d", len(parent.children)) - } - if len(parent.children[0].inodes) != 2 { - t.Fatalf("exp=2; got=%d", len(parent.children[0].inodes)) - } - if len(parent.children[1].inodes) != 3 { - t.Fatalf("exp=3; got=%d", len(parent.children[1].inodes)) - } -} - -// Ensure that a page with the minimum number of inodes just returns a single node. -func TestNode_split_MinKeys(t *testing.T) { - // Create a node. - n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}} - n.put([]byte("00000001"), []byte("00000001"), []byte("0123456701234567"), 0, 0) - n.put([]byte("00000002"), []byte("00000002"), []byte("0123456701234567"), 0, 0) - - // Split. - n.split(20) - if n.parent != nil { - t.Fatalf("expected nil parent") - } -} - -// Ensure that a node that has keys that all fit on a page just returns one leaf. -func TestNode_split_SinglePage(t *testing.T) { - // Create a node. - n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}} - n.put([]byte("00000001"), []byte("00000001"), []byte("0123456701234567"), 0, 0) - n.put([]byte("00000002"), []byte("00000002"), []byte("0123456701234567"), 0, 0) - n.put([]byte("00000003"), []byte("00000003"), []byte("0123456701234567"), 0, 0) - n.put([]byte("00000004"), []byte("00000004"), []byte("0123456701234567"), 0, 0) - n.put([]byte("00000005"), []byte("00000005"), []byte("0123456701234567"), 0, 0) - - // Split. - n.split(4096) - if n.parent != nil { - t.Fatalf("expected nil parent") - } -} diff --git a/_third_party/github.com/boltdb/bolt/page.go b/_third_party/github.com/boltdb/bolt/page.go index b3dc473..58e43c4 100644 --- a/_third_party/github.com/boltdb/bolt/page.go +++ b/_third_party/github.com/boltdb/bolt/page.go @@ -8,7 +8,6 @@ import ( const pageHeaderSize = int(unsafe.Offsetof(((*page)(nil)).ptr)) -const maxAllocSize = 0xFFFFFFF const minKeysPerPage = 2 const branchPageElementSize = int(unsafe.Sizeof(branchPageElement{})) diff --git a/_third_party/github.com/boltdb/bolt/page_test.go b/_third_party/github.com/boltdb/bolt/page_test.go deleted file mode 100644 index 7a4d327..0000000 --- a/_third_party/github.com/boltdb/bolt/page_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package bolt - -import ( - "testing" -) - -// Ensure that the page type can be returned in human readable format. -func TestPage_typ(t *testing.T) { - if typ := (&page{flags: branchPageFlag}).typ(); typ != "branch" { - t.Fatalf("exp=branch; got=%v", typ) - } - if typ := (&page{flags: leafPageFlag}).typ(); typ != "leaf" { - t.Fatalf("exp=leaf; got=%v", typ) - } - if typ := (&page{flags: metaPageFlag}).typ(); typ != "meta" { - t.Fatalf("exp=meta; got=%v", typ) - } - if typ := (&page{flags: freelistPageFlag}).typ(); typ != "freelist" { - t.Fatalf("exp=freelist; got=%v", typ) - } - if typ := (&page{flags: 20000}).typ(); typ != "unknown<4e20>" { - t.Fatalf("exp=unknown<4e20>; got=%v", typ) - } -} - -// Ensure that the hexdump debugging function doesn't blow up. -func TestPage_dump(t *testing.T) { - (&page{id: 256}).hexdump(16) -} diff --git a/_third_party/github.com/boltdb/bolt/quick_test.go b/_third_party/github.com/boltdb/bolt/quick_test.go deleted file mode 100644 index 4da5817..0000000 --- a/_third_party/github.com/boltdb/bolt/quick_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package bolt_test - -import ( - "bytes" - "flag" - "fmt" - "math/rand" - "os" - "reflect" - "testing/quick" - "time" -) - -// testing/quick defaults to 5 iterations and a random seed. -// You can override these settings from the command line: -// -// -quick.count The number of iterations to perform. -// -quick.seed The seed to use for randomizing. -// -quick.maxitems The maximum number of items to insert into a DB. -// -quick.maxksize The maximum size of a key. -// -quick.maxvsize The maximum size of a value. -// - -var qcount, qseed, qmaxitems, qmaxksize, qmaxvsize int - -func init() { - flag.IntVar(&qcount, "quick.count", 5, "") - flag.IntVar(&qseed, "quick.seed", int(time.Now().UnixNano())%100000, "") - flag.IntVar(&qmaxitems, "quick.maxitems", 1000, "") - flag.IntVar(&qmaxksize, "quick.maxksize", 1024, "") - flag.IntVar(&qmaxvsize, "quick.maxvsize", 1024, "") - flag.Parse() - fmt.Fprintln(os.Stderr, "seed:", qseed) - fmt.Fprintf(os.Stderr, "quick settings: count=%v, items=%v, ksize=%v, vsize=%v\n", qcount, qmaxitems, qmaxksize, qmaxvsize) -} - -func qconfig() *quick.Config { - return &quick.Config{ - MaxCount: qcount, - Rand: rand.New(rand.NewSource(int64(qseed))), - } -} - -type testdata []testdataitem - -func (t testdata) Len() int { return len(t) } -func (t testdata) Swap(i, j int) { t[i], t[j] = t[j], t[i] } -func (t testdata) Less(i, j int) bool { return bytes.Compare(t[i].Key, t[j].Key) == -1 } - -func (t testdata) Generate(rand *rand.Rand, size int) reflect.Value { - n := rand.Intn(qmaxitems-1) + 1 - items := make(testdata, n) - for i := 0; i < n; i++ { - item := &items[i] - item.Key = randByteSlice(rand, 1, qmaxksize) - item.Value = randByteSlice(rand, 0, qmaxvsize) - } - return reflect.ValueOf(items) -} - -type revtestdata []testdataitem - -func (t revtestdata) Len() int { return len(t) } -func (t revtestdata) Swap(i, j int) { t[i], t[j] = t[j], t[i] } -func (t revtestdata) Less(i, j int) bool { return bytes.Compare(t[i].Key, t[j].Key) == 1 } - -type testdataitem struct { - Key []byte - Value []byte -} - -func randByteSlice(rand *rand.Rand, minSize, maxSize int) []byte { - n := rand.Intn(maxSize-minSize) + minSize - b := make([]byte, n) - for i := 0; i < n; i++ { - b[i] = byte(rand.Intn(255)) - } - return b -} diff --git a/_third_party/github.com/boltdb/bolt/simulation_test.go b/_third_party/github.com/boltdb/bolt/simulation_test.go deleted file mode 100644 index dbbcb48..0000000 --- a/_third_party/github.com/boltdb/bolt/simulation_test.go +++ /dev/null @@ -1,327 +0,0 @@ -package bolt_test - -import ( - "bytes" - "fmt" - "math/rand" - "sync" - "testing" - - "github.com/mjibson/mog/_third_party/github.com/boltdb/bolt" -) - -func TestSimulate_1op_1p(t *testing.T) { testSimulate(t, 100, 1) } -func TestSimulate_10op_1p(t *testing.T) { testSimulate(t, 10, 1) } -func TestSimulate_100op_1p(t *testing.T) { testSimulate(t, 100, 1) } -func TestSimulate_1000op_1p(t *testing.T) { testSimulate(t, 1000, 1) } -func TestSimulate_10000op_1p(t *testing.T) { testSimulate(t, 10000, 1) } - -func TestSimulate_10op_10p(t *testing.T) { testSimulate(t, 10, 10) } -func TestSimulate_100op_10p(t *testing.T) { testSimulate(t, 100, 10) } -func TestSimulate_1000op_10p(t *testing.T) { testSimulate(t, 1000, 10) } -func TestSimulate_10000op_10p(t *testing.T) { testSimulate(t, 10000, 10) } - -func TestSimulate_100op_100p(t *testing.T) { testSimulate(t, 100, 100) } -func TestSimulate_1000op_100p(t *testing.T) { testSimulate(t, 1000, 100) } -func TestSimulate_10000op_100p(t *testing.T) { testSimulate(t, 10000, 100) } - -func TestSimulate_10000op_1000p(t *testing.T) { testSimulate(t, 10000, 1000) } - -// Randomly generate operations on a given database with multiple clients to ensure consistency and thread safety. -func testSimulate(t *testing.T, threadCount, parallelism int) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - rand.Seed(int64(qseed)) - - // A list of operations that readers and writers can perform. - var readerHandlers = []simulateHandler{simulateGetHandler} - var writerHandlers = []simulateHandler{simulateGetHandler, simulatePutHandler} - - var versions = make(map[int]*QuickDB) - versions[1] = NewQuickDB() - - db := NewTestDB() - defer db.Close() - - var mutex sync.Mutex - - // Run n threads in parallel, each with their own operation. - var wg sync.WaitGroup - var threads = make(chan bool, parallelism) - var i int - for { - threads <- true - wg.Add(1) - writable := ((rand.Int() % 100) < 20) // 20% writers - - // Choose an operation to execute. - var handler simulateHandler - if writable { - handler = writerHandlers[rand.Intn(len(writerHandlers))] - } else { - handler = readerHandlers[rand.Intn(len(readerHandlers))] - } - - // Execute a thread for the given operation. - go func(writable bool, handler simulateHandler) { - defer wg.Done() - - // Start transaction. - tx, err := db.Begin(writable) - if err != nil { - t.Fatal("tx begin: ", err) - } - - // Obtain current state of the dataset. - mutex.Lock() - var qdb = versions[tx.ID()] - if writable { - qdb = versions[tx.ID()-1].Copy() - } - mutex.Unlock() - - // Make sure we commit/rollback the tx at the end and update the state. - if writable { - defer func() { - mutex.Lock() - versions[tx.ID()] = qdb - mutex.Unlock() - - ok(t, tx.Commit()) - }() - } else { - defer tx.Rollback() - } - - // Ignore operation if we don't have data yet. - if qdb == nil { - return - } - - // Execute handler. - handler(tx, qdb) - - // Release a thread back to the scheduling loop. - <-threads - }(writable, handler) - - i++ - if i > threadCount { - break - } - } - - // Wait until all threads are done. - wg.Wait() -} - -type simulateHandler func(tx *bolt.Tx, qdb *QuickDB) - -// Retrieves a key from the database and verifies that it is what is expected. -func simulateGetHandler(tx *bolt.Tx, qdb *QuickDB) { - // Randomly retrieve an existing exist. - keys := qdb.Rand() - if len(keys) == 0 { - return - } - - // Retrieve root bucket. - b := tx.Bucket(keys[0]) - if b == nil { - panic(fmt.Sprintf("bucket[0] expected: %08x\n", trunc(keys[0], 4))) - } - - // Drill into nested buckets. - for _, key := range keys[1 : len(keys)-1] { - b = b.Bucket(key) - if b == nil { - panic(fmt.Sprintf("bucket[n] expected: %v -> %v\n", keys, key)) - } - } - - // Verify key/value on the final bucket. - expected := qdb.Get(keys) - actual := b.Get(keys[len(keys)-1]) - if !bytes.Equal(actual, expected) { - fmt.Println("=== EXPECTED ===") - fmt.Println(expected) - fmt.Println("=== ACTUAL ===") - fmt.Println(actual) - fmt.Println("=== END ===") - panic("value mismatch") - } -} - -// Inserts a key into the database. -func simulatePutHandler(tx *bolt.Tx, qdb *QuickDB) { - var err error - keys, value := randKeys(), randValue() - - // Retrieve root bucket. - b := tx.Bucket(keys[0]) - if b == nil { - b, err = tx.CreateBucket(keys[0]) - if err != nil { - panic("create bucket: " + err.Error()) - } - } - - // Create nested buckets, if necessary. - for _, key := range keys[1 : len(keys)-1] { - child := b.Bucket(key) - if child != nil { - b = child - } else { - b, err = b.CreateBucket(key) - if err != nil { - panic("create bucket: " + err.Error()) - } - } - } - - // Insert into database. - if err := b.Put(keys[len(keys)-1], value); err != nil { - panic("put: " + err.Error()) - } - - // Insert into in-memory database. - qdb.Put(keys, value) -} - -// QuickDB is an in-memory database that replicates the functionality of the -// Bolt DB type except that it is entirely in-memory. It is meant for testing -// that the Bolt database is consistent. -type QuickDB struct { - sync.RWMutex - m map[string]interface{} -} - -// NewQuickDB returns an instance of QuickDB. -func NewQuickDB() *QuickDB { - return &QuickDB{m: make(map[string]interface{})} -} - -// Get retrieves the value at a key path. -func (db *QuickDB) Get(keys [][]byte) []byte { - db.RLock() - defer db.RUnlock() - - m := db.m - for _, key := range keys[:len(keys)-1] { - value := m[string(key)] - if value == nil { - return nil - } - switch value := value.(type) { - case map[string]interface{}: - m = value - case []byte: - return nil - } - } - - // Only return if it's a simple value. - if value, ok := m[string(keys[len(keys)-1])].([]byte); ok { - return value - } - return nil -} - -// Put inserts a value into a key path. -func (db *QuickDB) Put(keys [][]byte, value []byte) { - db.Lock() - defer db.Unlock() - - // Build buckets all the way down the key path. - m := db.m - for _, key := range keys[:len(keys)-1] { - if _, ok := m[string(key)].([]byte); ok { - return // Keypath intersects with a simple value. Do nothing. - } - - if m[string(key)] == nil { - m[string(key)] = make(map[string]interface{}) - } - m = m[string(key)].(map[string]interface{}) - } - - // Insert value into the last key. - m[string(keys[len(keys)-1])] = value -} - -// Rand returns a random key path that points to a simple value. -func (db *QuickDB) Rand() [][]byte { - db.RLock() - defer db.RUnlock() - if len(db.m) == 0 { - return nil - } - var keys [][]byte - db.rand(db.m, &keys) - return keys -} - -func (db *QuickDB) rand(m map[string]interface{}, keys *[][]byte) { - i, index := 0, rand.Intn(len(m)) - for k, v := range m { - if i == index { - *keys = append(*keys, []byte(k)) - if v, ok := v.(map[string]interface{}); ok { - db.rand(v, keys) - } - return - } - i++ - } - panic("quickdb rand: out-of-range") -} - -// Copy copies the entire database. -func (db *QuickDB) Copy() *QuickDB { - db.RLock() - defer db.RUnlock() - return &QuickDB{m: db.copy(db.m)} -} - -func (db *QuickDB) copy(m map[string]interface{}) map[string]interface{} { - clone := make(map[string]interface{}, len(m)) - for k, v := range m { - switch v := v.(type) { - case map[string]interface{}: - clone[k] = db.copy(v) - default: - clone[k] = v - } - } - return clone -} - -func randKey() []byte { - var min, max = 1, 1024 - n := rand.Intn(max-min) + min - b := make([]byte, n) - for i := 0; i < n; i++ { - b[i] = byte(rand.Intn(255)) - } - return b -} - -func randKeys() [][]byte { - var keys [][]byte - var count = rand.Intn(2) + 2 - for i := 0; i < count; i++ { - keys = append(keys, randKey()) - } - return keys -} - -func randValue() []byte { - n := rand.Intn(8192) - b := make([]byte, n) - for i := 0; i < n; i++ { - b[i] = byte(rand.Intn(255)) - } - return b -} diff --git a/_third_party/github.com/boltdb/bolt/tx.go b/_third_party/github.com/boltdb/bolt/tx.go index c041d73..fda6a21 100644 --- a/_third_party/github.com/boltdb/bolt/tx.go +++ b/_third_party/github.com/boltdb/bolt/tx.go @@ -252,37 +252,42 @@ func (tx *Tx) close() { } // Copy writes the entire database to a writer. -// A reader transaction is maintained during the copy so it is safe to continue -// using the database while a copy is in progress. -// Copy will write exactly tx.Size() bytes into the writer. +// This function exists for backwards compatibility. Use WriteTo() in func (tx *Tx) Copy(w io.Writer) error { - var f *os.File - var err error + _, err := tx.WriteTo(w) + return err +} +// WriteTo writes the entire database to a writer. +// If err == nil then exactly tx.Size() bytes will be written into the writer. +func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) { // Attempt to open reader directly. + var f *os.File if f, err = os.OpenFile(tx.db.path, os.O_RDONLY|odirect, 0); err != nil { // Fallback to a regular open if that doesn't work. if f, err = os.OpenFile(tx.db.path, os.O_RDONLY, 0); err != nil { - return err + return 0, err } } // Copy the meta pages. tx.db.metalock.Lock() - _, err = io.CopyN(w, f, int64(tx.db.pageSize*2)) + n, err = io.CopyN(w, f, int64(tx.db.pageSize*2)) tx.db.metalock.Unlock() if err != nil { _ = f.Close() - return fmt.Errorf("meta copy: %s", err) + return n, fmt.Errorf("meta copy: %s", err) } // Copy data pages. - if _, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2)); err != nil { + wn, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2)) + n += wn + if err != nil { _ = f.Close() - return err + return n, err } - return f.Close() + return n, f.Close() } // CopyFile copies the entire database to file at the given path. diff --git a/_third_party/github.com/boltdb/bolt/tx_test.go b/_third_party/github.com/boltdb/bolt/tx_test.go deleted file mode 100644 index f750a38..0000000 --- a/_third_party/github.com/boltdb/bolt/tx_test.go +++ /dev/null @@ -1,424 +0,0 @@ -package bolt_test - -import ( - "errors" - "fmt" - "os" - "testing" - - "github.com/mjibson/mog/_third_party/github.com/boltdb/bolt" -) - -// Ensure that committing a closed transaction returns an error. -func TestTx_Commit_Closed(t *testing.T) { - db := NewTestDB() - defer db.Close() - tx, _ := db.Begin(true) - tx.CreateBucket([]byte("foo")) - ok(t, tx.Commit()) - equals(t, tx.Commit(), bolt.ErrTxClosed) -} - -// Ensure that rolling back a closed transaction returns an error. -func TestTx_Rollback_Closed(t *testing.T) { - db := NewTestDB() - defer db.Close() - tx, _ := db.Begin(true) - ok(t, tx.Rollback()) - equals(t, tx.Rollback(), bolt.ErrTxClosed) -} - -// Ensure that committing a read-only transaction returns an error. -func TestTx_Commit_ReadOnly(t *testing.T) { - db := NewTestDB() - defer db.Close() - tx, _ := db.Begin(false) - equals(t, tx.Commit(), bolt.ErrTxNotWritable) -} - -// Ensure that a transaction can retrieve a cursor on the root bucket. -func TestTx_Cursor(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.CreateBucket([]byte("woojits")) - c := tx.Cursor() - - k, v := c.First() - equals(t, "widgets", string(k)) - assert(t, v == nil, "") - - k, v = c.Next() - equals(t, "woojits", string(k)) - assert(t, v == nil, "") - - k, v = c.Next() - assert(t, k == nil, "") - assert(t, v == nil, "") - - return nil - }) -} - -// Ensure that creating a bucket with a read-only transaction returns an error. -func TestTx_CreateBucket_ReadOnly(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.View(func(tx *bolt.Tx) error { - b, err := tx.CreateBucket([]byte("foo")) - assert(t, b == nil, "") - equals(t, bolt.ErrTxNotWritable, err) - return nil - }) -} - -// Ensure that creating a bucket on a closed transaction returns an error. -func TestTx_CreateBucket_Closed(t *testing.T) { - db := NewTestDB() - defer db.Close() - tx, _ := db.Begin(true) - tx.Commit() - b, err := tx.CreateBucket([]byte("foo")) - assert(t, b == nil, "") - equals(t, bolt.ErrTxClosed, err) -} - -// Ensure that a Tx can retrieve a bucket. -func TestTx_Bucket(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - b := tx.Bucket([]byte("widgets")) - assert(t, b != nil, "") - return nil - }) -} - -// Ensure that a Tx retrieving a non-existent key returns nil. -func TestTx_Get_Missing(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) - value := tx.Bucket([]byte("widgets")).Get([]byte("no_such_key")) - assert(t, value == nil, "") - return nil - }) -} - -// Ensure that a bucket can be created and retrieved. -func TestTx_CreateBucket(t *testing.T) { - db := NewTestDB() - defer db.Close() - - // Create a bucket. - db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucket([]byte("widgets")) - assert(t, b != nil, "") - ok(t, err) - return nil - }) - - // Read the bucket through a separate transaction. - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("widgets")) - assert(t, b != nil, "") - return nil - }) -} - -// Ensure that a bucket can be created if it doesn't already exist. -func TestTx_CreateBucketIfNotExists(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucketIfNotExists([]byte("widgets")) - assert(t, b != nil, "") - ok(t, err) - - b, err = tx.CreateBucketIfNotExists([]byte("widgets")) - assert(t, b != nil, "") - ok(t, err) - - b, err = tx.CreateBucketIfNotExists([]byte{}) - assert(t, b == nil, "") - equals(t, bolt.ErrBucketNameRequired, err) - - b, err = tx.CreateBucketIfNotExists(nil) - assert(t, b == nil, "") - equals(t, bolt.ErrBucketNameRequired, err) - return nil - }) - - // Read the bucket through a separate transaction. - db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("widgets")) - assert(t, b != nil, "") - return nil - }) -} - -// Ensure that a bucket cannot be created twice. -func TestTx_CreateBucket_Exists(t *testing.T) { - db := NewTestDB() - defer db.Close() - // Create a bucket. - db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucket([]byte("widgets")) - assert(t, b != nil, "") - ok(t, err) - return nil - }) - - // Create the same bucket again. - db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucket([]byte("widgets")) - assert(t, b == nil, "") - equals(t, bolt.ErrBucketExists, err) - return nil - }) -} - -// Ensure that a bucket is created with a non-blank name. -func TestTx_CreateBucket_NameRequired(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucket(nil) - assert(t, b == nil, "") - equals(t, bolt.ErrBucketNameRequired, err) - return nil - }) -} - -// Ensure that a bucket can be deleted. -func TestTx_DeleteBucket(t *testing.T) { - db := NewTestDB() - defer db.Close() - - // Create a bucket and add a value. - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) - return nil - }) - - // Delete the bucket and make sure we can't get the value. - db.Update(func(tx *bolt.Tx) error { - ok(t, tx.DeleteBucket([]byte("widgets"))) - assert(t, tx.Bucket([]byte("widgets")) == nil, "") - return nil - }) - - db.Update(func(tx *bolt.Tx) error { - // Create the bucket again and make sure there's not a phantom value. - b, err := tx.CreateBucket([]byte("widgets")) - assert(t, b != nil, "") - ok(t, err) - assert(t, tx.Bucket([]byte("widgets")).Get([]byte("foo")) == nil, "") - return nil - }) -} - -// Ensure that deleting a bucket on a closed transaction returns an error. -func TestTx_DeleteBucket_Closed(t *testing.T) { - db := NewTestDB() - defer db.Close() - tx, _ := db.Begin(true) - tx.Commit() - equals(t, tx.DeleteBucket([]byte("foo")), bolt.ErrTxClosed) -} - -// Ensure that deleting a bucket with a read-only transaction returns an error. -func TestTx_DeleteBucket_ReadOnly(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.View(func(tx *bolt.Tx) error { - equals(t, tx.DeleteBucket([]byte("foo")), bolt.ErrTxNotWritable) - return nil - }) -} - -// Ensure that nothing happens when deleting a bucket that doesn't exist. -func TestTx_DeleteBucket_NotFound(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - equals(t, bolt.ErrBucketNotFound, tx.DeleteBucket([]byte("widgets"))) - return nil - }) -} - -// Ensure that Tx commit handlers are called after a transaction successfully commits. -func TestTx_OnCommit(t *testing.T) { - var x int - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.OnCommit(func() { x += 1 }) - tx.OnCommit(func() { x += 2 }) - _, err := tx.CreateBucket([]byte("widgets")) - return err - }) - equals(t, 3, x) -} - -// Ensure that Tx commit handlers are NOT called after a transaction rolls back. -func TestTx_OnCommit_Rollback(t *testing.T) { - var x int - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.OnCommit(func() { x += 1 }) - tx.OnCommit(func() { x += 2 }) - tx.CreateBucket([]byte("widgets")) - return errors.New("rollback this commit") - }) - equals(t, 0, x) -} - -// Ensure that the database can be copied to a file path. -func TestTx_CopyFile(t *testing.T) { - db := NewTestDB() - defer db.Close() - var dest = tempfile() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) - tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("bat")) - return nil - }) - - ok(t, db.View(func(tx *bolt.Tx) error { return tx.CopyFile(dest, 0600) })) - - db2, err := bolt.Open(dest, 0600, nil) - ok(t, err) - defer db2.Close() - - db2.View(func(tx *bolt.Tx) error { - equals(t, []byte("bar"), tx.Bucket([]byte("widgets")).Get([]byte("foo"))) - equals(t, []byte("bat"), tx.Bucket([]byte("widgets")).Get([]byte("baz"))) - return nil - }) -} - -type failWriterError struct{} - -func (failWriterError) Error() string { - return "error injected for tests" -} - -type failWriter struct { - // fail after this many bytes - After int -} - -func (f *failWriter) Write(p []byte) (n int, err error) { - n = len(p) - if n > f.After { - n = f.After - err = failWriterError{} - } - f.After -= n - return n, err -} - -// Ensure that Copy handles write errors right. -func TestTx_CopyFile_Error_Meta(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) - tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("bat")) - return nil - }) - - err := db.View(func(tx *bolt.Tx) error { return tx.Copy(&failWriter{}) }) - equals(t, err.Error(), "meta copy: error injected for tests") -} - -// Ensure that Copy handles write errors right. -func TestTx_CopyFile_Error_Normal(t *testing.T) { - db := NewTestDB() - defer db.Close() - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) - tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("bat")) - return nil - }) - - err := db.View(func(tx *bolt.Tx) error { return tx.Copy(&failWriter{3 * db.Info().PageSize}) }) - equals(t, err.Error(), "error injected for tests") -} - -func ExampleTx_Rollback() { - // Open the database. - db, _ := bolt.Open(tempfile(), 0666, nil) - defer os.Remove(db.Path()) - defer db.Close() - - // Create a bucket. - db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("widgets")) - return err - }) - - // Set a value for a key. - db.Update(func(tx *bolt.Tx) error { - return tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) - }) - - // Update the key but rollback the transaction so it never saves. - tx, _ := db.Begin(true) - b := tx.Bucket([]byte("widgets")) - b.Put([]byte("foo"), []byte("baz")) - tx.Rollback() - - // Ensure that our original value is still set. - db.View(func(tx *bolt.Tx) error { - value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) - fmt.Printf("The value for 'foo' is still: %s\n", value) - return nil - }) - - // Output: - // The value for 'foo' is still: bar -} - -func ExampleTx_CopyFile() { - // Open the database. - db, _ := bolt.Open(tempfile(), 0666, nil) - defer os.Remove(db.Path()) - defer db.Close() - - // Create a bucket and a key. - db.Update(func(tx *bolt.Tx) error { - tx.CreateBucket([]byte("widgets")) - tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) - return nil - }) - - // Copy the database to another file. - toFile := tempfile() - db.View(func(tx *bolt.Tx) error { return tx.CopyFile(toFile, 0666) }) - defer os.Remove(toFile) - - // Open the cloned database. - db2, _ := bolt.Open(toFile, 0666, nil) - defer db2.Close() - - // Ensure that the key exists in the copy. - db2.View(func(tx *bolt.Tx) error { - value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) - fmt.Printf("The value for 'foo' in the clone is: %s\n", value) - return nil - }) - - // Output: - // The value for 'foo' in the clone is: bar -} diff --git a/_third_party/github.com/julienschmidt/httprouter/LICENSE b/_third_party/github.com/julienschmidt/httprouter/LICENSE deleted file mode 100644 index b829abc..0000000 --- a/_third_party/github.com/julienschmidt/httprouter/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2013 Julien Schmidt. All rights reserved. - - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The names of the contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL JULIEN SCHMIDT BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/_third_party/github.com/julienschmidt/httprouter/README.md b/_third_party/github.com/julienschmidt/httprouter/README.md deleted file mode 100644 index 5a25d24..0000000 --- a/_third_party/github.com/julienschmidt/httprouter/README.md +++ /dev/null @@ -1,313 +0,0 @@ -# HttpRouter [![Build Status](https://travis-ci.org/julienschmidt/httprouter.png?branch=master)](https://travis-ci.org/julienschmidt/httprouter) [![GoDoc](http://godoc.org/github.com/julienschmidt/httprouter?status.png)](http://godoc.org/github.com/julienschmidt/httprouter) - -HttpRouter is a lightweight high performance HTTP request router -(also called *multiplexer* or just *mux* for short) for [Go](http://golang.org/). - -In contrast to the default mux of Go's net/http package, this router supports -variables in the routing pattern and matches against the request method. -It also scales better. - -The router is optimized for best performance and a small memory footprint. -It scales well even with very long paths and a large number of routes. -A compressing dynamic trie (radix tree) structure is used for efficient matching. - -## Features -**Zero Garbage:** The matching and dispatching process generates zero bytes of -garbage. In fact, the only heap allocations that are made, is by building the -slice of the key-value pairs for path parameters. If the request path contains -no parameters, not a single heap allocation is necessary. - -**Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark). -See below for technical details of the implementation. - -**Parameters in your routing pattern:** Stop parsing the requested URL path, -just give the path segment a name and the router delivers the dynamic value to -you. Because of the design of the router, path parameters are very cheap. - -**Only explicit matches:** With other routers, like [http.ServeMux](http://golang.org/pkg/net/http/#ServeMux), -a requested URL path could match multiple patterns. Therefore they have some -awkward pattern priority rules, like *longest match* or *first registered, -first matched*. By design of this router, a request can only match exactly one -or no route. As a result, there are also no unintended matches, which makes it -great for SEO and improves the user experience. - -**Stop caring about trailing slashes:** Choose the URL style you like, the -router automatically redirects the client if a trailing slash is missing or if -there is one extra. Of course it only does so, if the new path has a handler. -If you don't like it, you can turn off this behavior. - -**Path auto-correction:** Besides detecting the missing or additional trailing -slash at no extra cost, the router can also fix wrong cases and remove -superfluous path elements (like `../` or `//`). -Is [CAPTAIN CAPS LOCK](http://www.urbandictionary.com/define.php?term=Captain+Caps+Lock) one of your users? -HttpRouter can help him by making a case-insensitive look-up and redirecting him -to the correct URL. - -**No more server crashes:** You can set a PanicHandler to deal with panics -occurring during handling a HTTP request. The router then recovers and lets the -PanicHandler log what happened and deliver a nice error page. - -Of course you can also set a **custom NotFound handler** and **serve static files**. - -## Usage -This is just a quick introduction, view the [GoDoc](http://godoc.org/github.com/julienschmidt/httprouter) for details. - -Let's start with a trivial example: -```go -package main - -import ( - "fmt" - "github.com/julienschmidt/httprouter" - "net/http" - "log" -) - -func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprint(w, "Welcome!\n") -} - -func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) -} - -func main() { - router := httprouter.New() - router.GET("/", Index) - router.GET("/hello/:name", Hello) - - log.Fatal(http.ListenAndServe(":8080", router)) -} -``` - -### Named parameters -As you can see, `:name` is a *named parameter*. -The values are accessible via `httprouter.Params`, which is just a slice of `httprouter.Param`s. -You can get the value of a parameter either by its index in the slice, or by using the `ByName(name)` method: -`:name` can be retrived by `ByName("name")`. - -Named parameters only match a single path segment: -``` -Pattern: /user/:user - - /user/gordon match - /user/you match - /user/gordon/profile no match - /user/ no match -``` - -**Note:** Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns `/user/new` and `/user/:user` for the same request method at the same time. The routing of different request methods is independent from each other. - -### Catch-All parameters -The second type are *catch-all* parameters and have the form `*name`. -Like the name suggests, they match everything. -Therefore they must always be at the **end** of the pattern: -``` -Pattern: /src/*filepath - - /src/ match - /src/somefile.go match - /src/subdir/somefile.go match -``` - -## How does it work? -The router relies on a tree structure which makes heavy use of *common prefixes*, -it is basically a *compact* [*prefix tree*](http://en.wikipedia.org/wiki/Trie) -(or just [*Radix tree*](http://en.wikipedia.org/wiki/Radix_tree)). -Nodes with a common prefix also share a common parent. Here is a short example -what the routing tree for the `GET` request method could look like: - -``` -Priority Path Handle -9 \ *<1> -3 ├s nil -2 |├earch\ *<2> -1 |└upport\ *<3> -2 ├blog\ *<4> -1 | └:post nil -1 | └\ *<5> -2 ├about-us\ *<6> -1 | └team\ *<7> -1 └contact\ *<8> -``` -Every `*` represents the memory address of a handler function (a pointer). -If you follow a path trough the tree from the root to the leaf, you get the -complete route path, e.g `\blog\:post\`, where `:post` is just a placeholder -([*parameter*](#named-parameters)) for an actual post name. Unlike hash-maps, a -tree structure also allows us to use dynamic parts like the `:post` parameter, -since we actually match against the routing patterns instead of just comparing -hashes. [As benchmarks show](https://github.com/julienschmidt/go-http-routing-benchmark), -this works very well and efficient. - -Since URL paths have a hierarchical structure and make use only of a limited set -of characters (byte values), it is very likely that there are a lot of common -prefixes. This allows us to easily reduce the routing into ever smaller problems. -Moreover the router manages a separate tree for every request method. -For one thing it is more space efficient than holding a method->handle map in -every single node, for another thing is also allows us to greatly reduce the -routing problem before even starting the look-up in the prefix-tree. - -For even better scalability, the child nodes on each tree level are ordered by -priority, where the priority is just the number of handles registered in sub -nodes (children, grandchildren, and so on..). -This helps in two ways: - -1. Nodes which are part of the most routing paths are evaluated first. This -helps to make as much routes as possible to be reachable as fast as possible. -2. It is some sort of cost compensation. The longest reachable path (highest -cost) can always be evaluated first. The following scheme visualizes the tree -structure. Nodes are evaluated from top to bottom and from left to right. - -``` -├------------ -├--------- -├----- -├---- -├-- -├-- -└- -``` - - -## Why doesn't this work with http.Handler? -**It does!** The router itself implements the http.Handler interface. -Moreover the router provides convenient [adapters for http.Handler](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handler)s and [http.HandlerFunc](http://godoc.org/github.com/julienschmidt/httprouter#Router.HandlerFunc)s -which allows them to be used as a [httprouter.Handle](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) when registering a route. -The only disadvantage is, that no parameter values can be retrieved when a -http.Handler or http.HandlerFunc is used, since there is no efficient way to -pass the values with the existing function parameters. -Therefore [httprouter.Handle](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) has a third function parameter. - -Just try it out for yourself, the usage of HttpRouter is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up. - - -## Where can I find Middleware *X*? -This package just provides a very efficient request router with a few extra -features. The router is just a [http.Handler](http://golang.org/pkg/net/http/#Handler), -you can chain any http.Handler compatible middleware before the router, -for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers). -Or you could [just write your own](http://justinas.org/writing-http-middleware-in-go/), -it's very easy! - -Alternatively, you could try [a framework building upon HttpRouter](#web-frameworks--co-based-on-httprouter). - -### Multi-domain / Sub-domains -Here is a quick example: Does your server serve multiple domains / hosts? -You want to use sub-domains? -Define a router per host! -```go -// We need an object that implements the http.Handler interface. -// Therefore we need a type for which we implement the ServeHTTP method. -// We just use a map here, in which we map host names (with port) to http.Handlers -type HostSwitch map[string]http.Handler - -// Implement the ServerHTTP method on our new type -func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Check if a http.Handler is registered for the given host. - // If yes, use it to handle the request. - if handler := hs[r.Host]; handler != nil { - handler.ServeHTTP(w, r) - } else { - // Handle host names for wich no handler is registered - http.Error(w, "Forbidden", 403) // Or Redirect? - } -} - -func main() { - // Initialize a router as usual - router := httprouter.New() - router.GET("/", Index) - router.GET("/hello/:name", Hello) - - // Make a new HostSwitch and insert the router (our http handler) - // for example.com and port 12345 - hs := make(HostSwitch) - hs["example.com:12345"] = router - - // Use the HostSwitch to listen and serve on port 12345 - log.Fatal(http.ListenAndServe(":12345", hs)) -} -``` - -### Basic Authentication -Another quick example: Basic Authentification (RFC 2617) for handles: - -```go -package main - -import ( - "bytes" - "encoding/base64" - "fmt" - "github.com/julienschmidt/httprouter" - "net/http" - "log" - "strings" -) - -func BasicAuth(h httprouter.Handle, user, pass []byte) httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - const basicAuthPrefix string = "Basic " - - // Get the Basic Authentication credentials - auth := r.Header.Get("Authorization") - if strings.HasPrefix(auth, basicAuthPrefix) { - // Check credentials - payload, err := base64.StdEncoding.DecodeString(auth[len(basicAuthPrefix):]) - if err == nil { - pair := bytes.SplitN(payload, []byte(":"), 2) - if len(pair) == 2 && bytes.Equal(pair[0], user) && bytes.Equal(pair[1], pass) { - // Delegate request to the given handle - h(w, r, ps) - return - } - } - } - - // Request Basic Authentication otherwise - w.Header().Set("WWW-Authenticate", "Basic realm=Restricted") - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - } -} - -func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprint(w, "Not protected!\n") -} - -func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprint(w, "Protected!\n") -} - -func main() { - user := []byte("gordon") - pass := []byte("secret!") - - router := httprouter.New() - router.GET("/", Index) - router.GET("/protected/", BasicAuth(Protected, user, pass)) - - log.Fatal(http.ListenAndServe(":8080", router)) -} -``` - -## Chaining with the NotFound handler - -**NOTE: It might be required to set [Router.HandleMethodNotAllowed](http://godoc.org/github.com/julienschmidt/httprouter#Router.HandleMethodNotAllowed) to `false` to avoid problems.** - -You can use another [http.HandlerFunc](http://golang.org/pkg/net/http/#HandlerFunc), for example another router, to handle requests which could not be matched by this router by using the [Router.NotFound](http://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) handler. This allows chaining. - -### Static files -The `NotFound` handler can for example be used to serve static files from the root path `/` (like an index.html file along with other assets): -```go -// Serve static files from the ./public directory -router.NotFound = http.FileServer(http.Dir("public")).ServeHTTP -``` - -But this approach sidesteps the strict core rules of this router to avoid routing problems. A cleaner approach is to use a distinct sub-path for serving files, like `/static/*filepath` or `/files/*filepath`. - -## Web Frameworks & Co based on HttpRouter -If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package: -* [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance -* [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine -* [Hitch](https://github.com/nbio/hitch): Hitch ties httprouter, [httpcontext](https://github.com/nbio/httpcontext), and middleware up in a bow -* [Neko](https://github.com/rocwong/neko): A lightweight web application framework for Golang diff --git a/_third_party/github.com/julienschmidt/httprouter/path_test.go b/_third_party/github.com/julienschmidt/httprouter/path_test.go deleted file mode 100644 index c4ceda5..0000000 --- a/_third_party/github.com/julienschmidt/httprouter/path_test.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2013 Julien Schmidt. All rights reserved. -// Based on the path package, Copyright 2009 The Go Authors. -// Use of this source code is governed by a BSD-style license that can be found -// in the LICENSE file. - -package httprouter - -import ( - "runtime" - "testing" -) - -var cleanTests = []struct { - path, result string -}{ - // Already clean - {"/", "/"}, - {"/abc", "/abc"}, - {"/a/b/c", "/a/b/c"}, - {"/abc/", "/abc/"}, - {"/a/b/c/", "/a/b/c/"}, - - // missing root - {"", "/"}, - {"abc", "/abc"}, - {"abc/def", "/abc/def"}, - {"a/b/c", "/a/b/c"}, - - // Remove doubled slash - {"//", "/"}, - {"/abc//", "/abc/"}, - {"/abc/def//", "/abc/def/"}, - {"/a/b/c//", "/a/b/c/"}, - {"/abc//def//ghi", "/abc/def/ghi"}, - {"//abc", "/abc"}, - {"///abc", "/abc"}, - {"//abc//", "/abc/"}, - - // Remove . elements - {".", "/"}, - {"./", "/"}, - {"/abc/./def", "/abc/def"}, - {"/./abc/def", "/abc/def"}, - {"/abc/.", "/abc/"}, - - // Remove .. elements - {"..", "/"}, - {"../", "/"}, - {"../../", "/"}, - {"../..", "/"}, - {"../../abc", "/abc"}, - {"/abc/def/ghi/../jkl", "/abc/def/jkl"}, - {"/abc/def/../ghi/../jkl", "/abc/jkl"}, - {"/abc/def/..", "/abc"}, - {"/abc/def/../..", "/"}, - {"/abc/def/../../..", "/"}, - {"/abc/def/../../..", "/"}, - {"/abc/def/../../../ghi/jkl/../../../mno", "/mno"}, - - // Combinations - {"abc/./../def", "/def"}, - {"abc//./../def", "/def"}, - {"abc/../../././../def", "/def"}, -} - -func TestPathClean(t *testing.T) { - for _, test := range cleanTests { - if s := CleanPath(test.path); s != test.result { - t.Errorf("CleanPath(%q) = %q, want %q", test.path, s, test.result) - } - if s := CleanPath(test.result); s != test.result { - t.Errorf("CleanPath(%q) = %q, want %q", test.result, s, test.result) - } - } -} - -func TestPathCleanMallocs(t *testing.T) { - if testing.Short() { - t.Skip("skipping malloc count in short mode") - } - if runtime.GOMAXPROCS(0) > 1 { - t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1") - return - } - - for _, test := range cleanTests { - allocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) }) - if allocs > 0 { - t.Errorf("CleanPath(%q): %v allocs, want zero", test.result, allocs) - } - } -} diff --git a/_third_party/github.com/julienschmidt/httprouter/router.go b/_third_party/github.com/julienschmidt/httprouter/router.go index 4893950..155b871 100644 --- a/_third_party/github.com/julienschmidt/httprouter/router.go +++ b/_third_party/github.com/julienschmidt/httprouter/router.go @@ -142,6 +142,11 @@ type Router struct { // found. If it is not set, http.NotFound is used. NotFound http.HandlerFunc + // Configurable http.HandlerFunc which is called when a request + // cannot be routed and HandleMethodNotAllowed is true. + // If it is not set, http.Error with http.StatusMethodNotAllowed is used. + MethodNotAllowed http.HandlerFunc + // Function to handle panics recovered from http handlers. // It should be used to generate a error page and return the http error code // 500 (Internal Server Error). @@ -173,6 +178,11 @@ func (r *Router) HEAD(path string, handle Handle) { r.Handle("HEAD", path, handle) } +// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) +func (r *Router) OPTIONS(path string, handle Handle) { + r.Handle("OPTIONS", path, handle) +} + // POST is a shortcut for router.Handle("POST", path, handle) func (r *Router) POST(path string, handle Handle) { r.Handle("POST", path, handle) @@ -203,7 +213,7 @@ func (r *Router) DELETE(path string, handle Handle) { // communication with a proxy). func (r *Router) Handle(method, path string, handle Handle) { if path[0] != '/' { - panic("path must begin with '/'") + panic("path must begin with '/' in path '" + path + "'") } if r.trees == nil { @@ -232,11 +242,7 @@ func (r *Router) Handler(method, path string, handler http.Handler) { // HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a // request handle. func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) { - r.Handle(method, path, - func(w http.ResponseWriter, req *http.Request, _ Params) { - handler(w, req) - }, - ) + r.Handler(method, path, handler) } // ServeFiles serves files from the given file system root. @@ -251,7 +257,7 @@ func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) { // router.ServeFiles("/src/*filepath", http.Dir("/var/www")) func (r *Router) ServeFiles(path string, root http.FileSystem) { if len(path) < 10 || path[len(path)-10:] != "/*filepath" { - panic("path must end with /*filepath") + panic("path must end with /*filepath in path '" + path + "'") } fileServer := http.FileServer(root) @@ -335,10 +341,14 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { handle, _, _ := r.trees[method].getValue(req.URL.Path) if handle != nil { - http.Error(w, - http.StatusText(http.StatusMethodNotAllowed), - http.StatusMethodNotAllowed, - ) + if r.MethodNotAllowed != nil { + r.MethodNotAllowed(w, req) + } else { + http.Error(w, + http.StatusText(http.StatusMethodNotAllowed), + http.StatusMethodNotAllowed, + ) + } return } } diff --git a/_third_party/github.com/julienschmidt/httprouter/router_test.go b/_third_party/github.com/julienschmidt/httprouter/router_test.go deleted file mode 100644 index 6292ba8..0000000 --- a/_third_party/github.com/julienschmidt/httprouter/router_test.go +++ /dev/null @@ -1,355 +0,0 @@ -// Copyright 2013 Julien Schmidt. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be found -// in the LICENSE file. - -package httprouter - -import ( - "errors" - "fmt" - "net/http" - "net/http/httptest" - "reflect" - "testing" -) - -type mockResponseWriter struct{} - -func (m *mockResponseWriter) Header() (h http.Header) { - return http.Header{} -} - -func (m *mockResponseWriter) Write(p []byte) (n int, err error) { - return len(p), nil -} - -func (m *mockResponseWriter) WriteString(s string) (n int, err error) { - return len(s), nil -} - -func (m *mockResponseWriter) WriteHeader(int) {} - -func TestParams(t *testing.T) { - ps := Params{ - Param{"param1", "value1"}, - Param{"param2", "value2"}, - Param{"param3", "value3"}, - } - for i := range ps { - if val := ps.ByName(ps[i].Key); val != ps[i].Value { - t.Errorf("Wrong value for %s: Got %s; Want %s", ps[i].Key, val, ps[i].Value) - } - } - if val := ps.ByName("noKey"); val != "" { - t.Errorf("Expected empty string for not found key; got: %s", val) - } -} - -func TestRouter(t *testing.T) { - router := New() - - routed := false - router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) { - routed = true - want := Params{Param{"name", "gopher"}} - if !reflect.DeepEqual(ps, want) { - t.Fatalf("wrong wildcard values: want %v, got %v", want, ps) - } - }) - - w := new(mockResponseWriter) - - req, _ := http.NewRequest("GET", "/user/gopher", nil) - router.ServeHTTP(w, req) - - if !routed { - t.Fatal("routing failed") - } -} - -type handlerStruct struct { - handeled *bool -} - -func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) { - *h.handeled = true -} - -func TestRouterAPI(t *testing.T) { - var get, head, post, put, patch, delete, handler, handlerFunc bool - - httpHandler := handlerStruct{&handler} - - router := New() - router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { - get = true - }) - router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { - head = true - }) - router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) { - post = true - }) - router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) { - put = true - }) - router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) { - patch = true - }) - router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) { - delete = true - }) - router.Handler("GET", "/Handler", httpHandler) - router.HandlerFunc("GET", "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) { - handlerFunc = true - }) - - w := new(mockResponseWriter) - - r, _ := http.NewRequest("GET", "/GET", nil) - router.ServeHTTP(w, r) - if !get { - t.Error("routing GET failed") - } - - r, _ = http.NewRequest("HEAD", "/GET", nil) - router.ServeHTTP(w, r) - if !head { - t.Error("routing HEAD failed") - } - - r, _ = http.NewRequest("POST", "/POST", nil) - router.ServeHTTP(w, r) - if !post { - t.Error("routing POST failed") - } - - r, _ = http.NewRequest("PUT", "/PUT", nil) - router.ServeHTTP(w, r) - if !put { - t.Error("routing PUT failed") - } - - r, _ = http.NewRequest("PATCH", "/PATCH", nil) - router.ServeHTTP(w, r) - if !patch { - t.Error("routing PATCH failed") - } - - r, _ = http.NewRequest("DELETE", "/DELETE", nil) - router.ServeHTTP(w, r) - if !delete { - t.Error("routing DELETE failed") - } - - r, _ = http.NewRequest("GET", "/Handler", nil) - router.ServeHTTP(w, r) - if !handler { - t.Error("routing Handler failed") - } - - r, _ = http.NewRequest("GET", "/HandlerFunc", nil) - router.ServeHTTP(w, r) - if !handlerFunc { - t.Error("routing HandlerFunc failed") - } -} - -func TestRouterRoot(t *testing.T) { - router := New() - recv := catchPanic(func() { - router.GET("noSlashRoot", nil) - }) - if recv == nil { - t.Fatal("registering path not beginning with '/' did not panic") - } -} - -func TestRouterNotAllowed(t *testing.T) { - handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {} - - router := New() - router.POST("/path", handlerFunc) - - // Test not allowed - r, _ := http.NewRequest("GET", "/path", nil) - w := httptest.NewRecorder() - router.ServeHTTP(w, r) - if !(w.Code == http.StatusMethodNotAllowed) { - t.Errorf("NotAllowed handling route %s failed: Code=%d, Header=%v", w.Code, w.Header()) - } -} - -func TestRouterNotFound(t *testing.T) { - handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {} - - router := New() - router.GET("/path", handlerFunc) - router.GET("/dir/", handlerFunc) - router.GET("/", handlerFunc) - - testRoutes := []struct { - route string - code int - header string - }{ - {"/path/", 301, "map[Location:[/path]]"}, // TSR -/ - {"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/ - {"", 301, "map[Location:[/]]"}, // TSR +/ - {"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case - {"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case - {"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/ - {"/DIR", 301, "map[Location:[/dir/]]"}, // Fixed Case +/ - {"/../path", 301, "map[Location:[/path]]"}, // CleanPath - {"/nope", 404, ""}, // NotFound - } - for _, tr := range testRoutes { - r, _ := http.NewRequest("GET", tr.route, nil) - w := httptest.NewRecorder() - router.ServeHTTP(w, r) - if !(w.Code == tr.code && (w.Code == 404 || fmt.Sprint(w.Header()) == tr.header)) { - t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header()) - } - } - - // Test custom not found handler - var notFound bool - router.NotFound = func(rw http.ResponseWriter, r *http.Request) { - rw.WriteHeader(404) - notFound = true - } - r, _ := http.NewRequest("GET", "/nope", nil) - w := httptest.NewRecorder() - router.ServeHTTP(w, r) - if !(w.Code == 404 && notFound == true) { - t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header()) - } - - // Test other method than GET (want 307 instead of 301) - router.PATCH("/path", handlerFunc) - r, _ = http.NewRequest("PATCH", "/path/", nil) - w = httptest.NewRecorder() - router.ServeHTTP(w, r) - if !(w.Code == 307 && fmt.Sprint(w.Header()) == "map[Location:[/path]]") { - t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header()) - } - - // Test special case where no node for the prefix "/" exists - router = New() - router.GET("/a", handlerFunc) - r, _ = http.NewRequest("GET", "/", nil) - w = httptest.NewRecorder() - router.ServeHTTP(w, r) - if !(w.Code == 404) { - t.Errorf("NotFound handling route / failed: Code=%d", w.Code) - } -} - -func TestRouterPanicHandler(t *testing.T) { - router := New() - panicHandled := false - - router.PanicHandler = func(rw http.ResponseWriter, r *http.Request, p interface{}) { - panicHandled = true - } - - router.Handle("PUT", "/user/:name", func(_ http.ResponseWriter, _ *http.Request, _ Params) { - panic("oops!") - }) - - w := new(mockResponseWriter) - req, _ := http.NewRequest("PUT", "/user/gopher", nil) - - defer func() { - if rcv := recover(); rcv != nil { - t.Fatal("handling panic failed") - } - }() - - router.ServeHTTP(w, req) - - if !panicHandled { - t.Fatal("simulating failed") - } -} - -func TestRouterLookup(t *testing.T) { - routed := false - wantHandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) { - routed = true - } - wantParams := Params{Param{"name", "gopher"}} - - router := New() - - // try empty router first - handle, _, tsr := router.Lookup("GET", "/nope") - if handle != nil { - t.Fatalf("Got handle for unregistered pattern: %v", handle) - } - if tsr { - t.Error("Got wrong TSR recommendation!") - } - - // insert route and try again - router.GET("/user/:name", wantHandle) - - handle, params, tsr := router.Lookup("GET", "/user/gopher") - if handle == nil { - t.Fatal("Got no handle!") - } else { - handle(nil, nil, nil) - if !routed { - t.Fatal("Routing failed!") - } - } - - if !reflect.DeepEqual(params, wantParams) { - t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params) - } - - handle, _, tsr = router.Lookup("GET", "/user/gopher/") - if handle != nil { - t.Fatalf("Got handle for unregistered pattern: %v", handle) - } - if !tsr { - t.Error("Got no TSR recommendation!") - } - - handle, _, tsr = router.Lookup("GET", "/nope") - if handle != nil { - t.Fatalf("Got handle for unregistered pattern: %v", handle) - } - if tsr { - t.Error("Got wrong TSR recommendation!") - } -} - -type mockFileSystem struct { - opened bool -} - -func (mfs *mockFileSystem) Open(name string) (http.File, error) { - mfs.opened = true - return nil, errors.New("this is just a mock") -} - -func TestRouterServeFiles(t *testing.T) { - router := New() - mfs := &mockFileSystem{} - - recv := catchPanic(func() { - router.ServeFiles("/noFilepath", mfs) - }) - if recv == nil { - t.Fatal("registering path not ending with '*filepath' did not panic") - } - - router.ServeFiles("/*filepath", mfs) - w := new(mockResponseWriter) - r, _ := http.NewRequest("GET", "/favicon.ico", nil) - router.ServeHTTP(w, r) - if !mfs.opened { - t.Error("serving file failed") - } -} diff --git a/_third_party/github.com/julienschmidt/httprouter/tree.go b/_third_party/github.com/julienschmidt/httprouter/tree.go index 121d0c3..a15bc2c 100644 --- a/_third_party/github.com/julienschmidt/httprouter/tree.go +++ b/_third_party/github.com/julienschmidt/httprouter/tree.go @@ -43,41 +43,48 @@ type node struct { wildChild bool nType nodeType maxParams uint8 - indices []byte + indices string children []*node handle Handle priority uint32 } // increments priority of the given child and reorders if necessary -func (n *node) incrementChildPrio(i int) int { - n.children[i].priority++ - prio := n.children[i].priority +func (n *node) incrementChildPrio(pos int) int { + n.children[pos].priority++ + prio := n.children[pos].priority // adjust position (move to front) - for j := i - 1; j >= 0 && n.children[j].priority < prio; j-- { + newPos := pos + for newPos > 0 && n.children[newPos-1].priority < prio { // swap node positions - tmpN := n.children[j] - n.children[j] = n.children[i] - n.children[i] = tmpN - tmpI := n.indices[j] - n.indices[j] = n.indices[i] - n.indices[i] = tmpI - - i-- + tmpN := n.children[newPos-1] + n.children[newPos-1] = n.children[newPos] + n.children[newPos] = tmpN + + newPos-- + } + + // build new index char string + if newPos != pos { + n.indices = n.indices[:newPos] + // unchanged prefix, might be empty + n.indices[pos:pos+1] + // the index char we move + n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos' } - return i + + return newPos } // addRoute adds a node with the given handle to the path. // Not concurrency-safe! func (n *node) addRoute(path string, handle Handle) { + fullPath := path n.priority++ numParams := countParams(path) // non-empty tree if len(n.path) > 0 || len(n.children) > 0 { - WALK: + walk: for { // Update maxParams of the current node if numParams > n.maxParams { @@ -85,10 +92,12 @@ func (n *node) addRoute(path string, handle Handle) { } // Find the longest common prefix. - // This also implies that the commom prefix contains no ':' or '*' - // since the existing key can't contain this chars. + // This also implies that the common prefix contains no ':' or '*' + // since the existing key can't contain those chars. i := 0 - for max := min(len(path), len(n.path)); i < max && path[i] == n.path[i]; i++ { + max := min(len(path), len(n.path)) + for i < max && path[i] == n.path[i] { + i++ } // Split edge @@ -110,7 +119,8 @@ func (n *node) addRoute(path string, handle Handle) { } n.children = []*node{&child} - n.indices = []byte{n.path[i]} + // []byte for proper unicode char conversion, see #65 + n.indices = string([]byte{n.path[i]}) n.path = path[:i] n.handle = nil n.wildChild = false @@ -134,11 +144,13 @@ func (n *node) addRoute(path string, handle Handle) { if len(path) >= len(n.path) && n.path == path[:len(n.path)] { // check for longer wildcard, e.g. :name and :names if len(n.path) >= len(path) || path[len(n.path)] == '/' { - continue WALK + continue walk } } - panic("conflict with wildcard route") + panic("path segment '" + path + + "' conflicts with existing wildcard '" + n.path + + "' in path '" + fullPath + "'") } c := path[0] @@ -147,21 +159,22 @@ func (n *node) addRoute(path string, handle Handle) { if n.nType == param && c == '/' && len(n.children) == 1 { n = n.children[0] n.priority++ - continue WALK + continue walk } // Check if a child with the next path byte exists - for i, index := range n.indices { - if c == index { + for i := 0; i < len(n.indices); i++ { + if c == n.indices[i] { i = n.incrementChildPrio(i) n = n.children[i] - continue WALK + continue walk } } // Otherwise insert it if c != ':' && c != '*' { - n.indices = append(n.indices, c) + // []byte for proper unicode char conversion, see #65 + n.indices += string([]byte{c}) child := &node{ maxParams: numParams, } @@ -169,24 +182,24 @@ func (n *node) addRoute(path string, handle Handle) { n.incrementChildPrio(len(n.indices) - 1) n = child } - n.insertChild(numParams, path, handle) + n.insertChild(numParams, path, fullPath, handle) return } else if i == len(path) { // Make node a (in-path) leaf if n.handle != nil { - panic("a Handle is already registered for this path") + panic("a handle is already registered for path ''" + fullPath + "'") } n.handle = handle } return } } else { // Empty tree - n.insertChild(numParams, path, handle) + n.insertChild(numParams, path, fullPath, handle) } } -func (n *node) insertChild(numParams uint8, path string, handle Handle) { - var offset int +func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle) { + var offset int // already handled bytes of the path // find prefix until first wildcard (beginning with ':'' or '*'') for i, max := 0, len(path); numParams > 0; i++ { @@ -195,20 +208,29 @@ func (n *node) insertChild(numParams uint8, path string, handle Handle) { continue } - // Check if this Node existing children which would be - // unreachable if we insert the wildcard here - if len(n.children) > 0 { - panic("wildcard route conflicts with existing children") - } - // find wildcard end (either '/' or path end) end := i + 1 for end < max && path[end] != '/' { - end++ + switch path[end] { + // the wildcard name must not contain ':' and '*' + case ':', '*': + panic("only one wildcard per path segment is allowed, has: '" + + path[i:] + "' in path '" + fullPath + "'") + default: + end++ + } } + // check if this Node existing children which would be + // unreachable if we insert the wildcard here + if len(n.children) > 0 { + panic("wildcard route '" + path[i:end] + + "' conflicts with existing children in path '" + fullPath + "'") + } + + // check if the wildcard has a name if end-i < 2 { - panic("wildcards must be named with a non-empty name") + panic("wildcards must be named with a non-empty name in path '" + fullPath + "'") } if c == ':' { // param @@ -244,17 +266,17 @@ func (n *node) insertChild(numParams uint8, path string, handle Handle) { } else { // catchAll if end != max || numParams > 1 { - panic("catch-all routes are only allowed at the end of the path") + panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") } if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { - panic("catch-all conflicts with existing handle for the path segment root") + panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'") } // currently fixed width 1 for '/' i-- if path[i] != '/' { - panic("no / before catch-all") + panic("no / before catch-all in path '" + fullPath + "'") } n.path = path[offset:i] @@ -266,7 +288,7 @@ func (n *node) insertChild(numParams uint8, path string, handle Handle) { maxParams: 1, } n.children = []*node{child} - n.indices = []byte{path[i]} + n.indices = string(path[i]) n = child n.priority++ @@ -305,8 +327,8 @@ walk: // Outer loop for walking the tree // to walk down the tree if !n.wildChild { c := path[0] - for i, index := range n.indices { - if c == index { + for i := 0; i < len(n.indices); i++ { + if c == n.indices[i] { n = n.children[i] continue walk } @@ -379,7 +401,7 @@ walk: // Outer loop for walking the tree return default: - panic("Invalid node type") + panic("invalid node type") } } } else if path == n.path { @@ -391,10 +413,10 @@ walk: // Outer loop for walking the tree // No handle found. Check if a handle for this path + a // trailing slash exists for trailing slash recommendation - for i, index := range n.indices { - if index == '/' { + for i := 0; i < len(n.indices); i++ { + if n.indices[i] == '/' { n = n.children[i] - tsr = (n.path == "/" && n.handle != nil) || + tsr = (len(n.path) == 1 && n.handle != nil) || (n.nType == catchAll && n.children[0].handle != nil) return } @@ -414,7 +436,7 @@ walk: // Outer loop for walking the tree // Makes a case-insensitive lookup of the given path and tries to find a handler. // It can optionally also fix trailing slashes. -// It returns the case-corrected path and a bool indicating wether the lookup +// It returns the case-corrected path and a bool indicating whether the lookup // was successful. func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) { ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory @@ -433,7 +455,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa for i, index := range n.indices { // must use recursive approach since both index and // ToLower(index) could exist. We must check both. - if r == unicode.ToLower(rune(index)) { + if r == unicode.ToLower(index) { out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash) if found { return append(ciPath, out...), true @@ -445,53 +467,52 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa // without a trailing slash if a leaf exists for that path found = (fixTrailingSlash && path == "/" && n.handle != nil) return + } - } else { - n = n.children[0] - - switch n.nType { - case param: - // find param end (either '/' or path end) - k := 0 - for k < len(path) && path[k] != '/' { - k++ - } + n = n.children[0] + switch n.nType { + case param: + // find param end (either '/' or path end) + k := 0 + for k < len(path) && path[k] != '/' { + k++ + } - // add param value to case insensitive path - ciPath = append(ciPath, path[:k]...) + // add param value to case insensitive path + ciPath = append(ciPath, path[:k]...) - // we need to go deeper! - if k < len(path) { - if len(n.children) > 0 { - path = path[k:] - n = n.children[0] - continue - } else { // ... but we can't - if fixTrailingSlash && len(path) == k+1 { - return ciPath, true - } - return - } + // we need to go deeper! + if k < len(path) { + if len(n.children) > 0 { + path = path[k:] + n = n.children[0] + continue } - if n.handle != nil { + // ... but we can't + if fixTrailingSlash && len(path) == k+1 { return ciPath, true - } else if fixTrailingSlash && len(n.children) == 1 { - // No handle found. Check if a handle for this path + a - // trailing slash exists - n = n.children[0] - if n.path == "/" && n.handle != nil { - return append(ciPath, '/'), true - } } return + } - case catchAll: - return append(ciPath, path...), true - - default: - panic("Invalid node type") + if n.handle != nil { + return ciPath, true + } else if fixTrailingSlash && len(n.children) == 1 { + // No handle found. Check if a handle for this path + a + // trailing slash exists + n = n.children[0] + if n.path == "/" && n.handle != nil { + return append(ciPath, '/'), true + } } + return + + case catchAll: + return append(ciPath, path...), true + + default: + panic("invalid node type") } } else { // We should have reached the node containing the handle. @@ -503,10 +524,10 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa // No handle found. // Try to fix the path by adding a trailing slash if fixTrailingSlash { - for i, index := range n.indices { - if index == '/' { + for i := 0; i < len(n.indices); i++ { + if n.indices[i] == '/' { n = n.children[i] - if (n.path == "/" && n.handle != nil) || + if (len(n.path) == 1 && n.handle != nil) || (n.nType == catchAll && n.children[0].handle != nil) { return append(ciPath, '/'), true } diff --git a/_third_party/github.com/julienschmidt/httprouter/tree_test.go b/_third_party/github.com/julienschmidt/httprouter/tree_test.go deleted file mode 100644 index ed1f9a8..0000000 --- a/_third_party/github.com/julienschmidt/httprouter/tree_test.go +++ /dev/null @@ -1,584 +0,0 @@ -// Copyright 2013 Julien Schmidt. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be found -// in the LICENSE file. - -package httprouter - -import ( - "fmt" - "net/http" - "reflect" - "strings" - "testing" -) - -func printChildren(n *node, prefix string) { - fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handle, n.wildChild, n.nType) - for l := len(n.path); l > 0; l-- { - prefix += " " - } - for _, child := range n.children { - printChildren(child, prefix) - } -} - -// Used as a workaround since we can't compare functions or their adresses -var fakeHandlerValue string - -func fakeHandler(val string) Handle { - return func(http.ResponseWriter, *http.Request, Params) { - fakeHandlerValue = val - } -} - -type testRequests []struct { - path string - nilHandler bool - route string - ps Params -} - -func checkRequests(t *testing.T, tree *node, requests testRequests) { - for _, request := range requests { - handler, ps, _ := tree.getValue(request.path) - - if handler == nil { - if !request.nilHandler { - t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path) - } - } else if request.nilHandler { - t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path) - } else { - handler(nil, nil, nil) - if fakeHandlerValue != request.route { - t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route) - } - } - - if !reflect.DeepEqual(ps, request.ps) { - t.Errorf("Params mismatch for route '%s'", request.path) - } - } -} - -func checkPriorities(t *testing.T, n *node) uint32 { - var prio uint32 - for i := range n.children { - prio += checkPriorities(t, n.children[i]) - } - - if n.handle != nil { - prio++ - } - - if n.priority != prio { - t.Errorf( - "priority mismatch for node '%s': is %d, should be %d", - n.path, n.priority, prio, - ) - } - - return prio -} - -func checkMaxParams(t *testing.T, n *node) uint8 { - var maxParams uint8 - for i := range n.children { - params := checkMaxParams(t, n.children[i]) - if params > maxParams { - maxParams = params - } - } - if n.nType != static && !n.wildChild { - maxParams++ - } - - if n.maxParams != maxParams { - t.Errorf( - "maxParams mismatch for node '%s': is %d, should be %d", - n.path, n.maxParams, maxParams, - ) - } - - return maxParams -} - -func TestCountParams(t *testing.T) { - if countParams("/path/:param1/static/*catch-all") != 2 { - t.Fail() - } - if countParams(strings.Repeat("/:param", 256)) != 255 { - t.Fail() - } -} - -func TestTreeAddAndGet(t *testing.T) { - tree := &node{} - - routes := [...]string{ - "/hi", - "/contact", - "/co", - "/c", - "/a", - "/ab", - "/doc/", - "/doc/go_faq.html", - "/doc/go1.html", - } - for _, route := range routes { - tree.addRoute(route, fakeHandler(route)) - } - - //printChildren(tree, "") - - checkRequests(t, tree, testRequests{ - {"/a", false, "/a", nil}, - {"/", true, "", nil}, - {"/hi", false, "/hi", nil}, - {"/contact", false, "/contact", nil}, - {"/co", false, "/co", nil}, - {"/con", true, "", nil}, // key mismatch - {"/cona", true, "", nil}, // key mismatch - {"/no", true, "", nil}, // no matching child - {"/ab", false, "/ab", nil}, - }) - - checkPriorities(t, tree) - checkMaxParams(t, tree) -} - -func TestTreeWildcard(t *testing.T) { - tree := &node{} - - routes := [...]string{ - "/", - "/cmd/:tool/:sub", - "/cmd/:tool/", - "/src/*filepath", - "/search/", - "/search/:query", - "/user_:name", - "/user_:name/about", - "/files/:dir/*filepath", - "/doc/", - "/doc/go_faq.html", - "/doc/go1.html", - "/info/:user/public", - "/info/:user/project/:project", - } - for _, route := range routes { - tree.addRoute(route, fakeHandler(route)) - } - - //printChildren(tree, "") - - checkRequests(t, tree, testRequests{ - {"/", false, "/", nil}, - {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}}, - {"/cmd/test", true, "", Params{Param{"tool", "test"}}}, - {"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{"tool", "test"}, Param{"sub", "3"}}}, - {"/src/", false, "/src/*filepath", Params{Param{"filepath", "/"}}}, - {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}}, - {"/search/", false, "/search/", nil}, - {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, - {"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, - {"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}}, - {"/user_gopher/about", false, "/user_:name/about", Params{Param{"name", "gopher"}}}, - {"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{"dir", "js"}, Param{"filepath", "/inc/framework.js"}}}, - {"/info/gordon/public", false, "/info/:user/public", Params{Param{"user", "gordon"}}}, - {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}}, - }) - - checkPriorities(t, tree) - checkMaxParams(t, tree) -} - -func catchPanic(testFunc func()) (recv interface{}) { - defer func() { - recv = recover() - }() - - testFunc() - return -} - -type testRoute struct { - path string - conflict bool -} - -func testRoutes(t *testing.T, routes []testRoute) { - tree := &node{} - - for _, route := range routes { - recv := catchPanic(func() { - tree.addRoute(route.path, nil) - }) - - if route.conflict { - if recv == nil { - t.Errorf("no panic for conflicting route '%s'", route.path) - } - } else if recv != nil { - t.Errorf("unexpected panic for route '%s': %v", route.path, recv) - } - } - - //printChildren(tree, "") -} - -func TestTreeWildcardConflict(t *testing.T) { - routes := []testRoute{ - {"/cmd/:tool/:sub", false}, - {"/cmd/vet", true}, - {"/src/*filepath", false}, - {"/src/*filepathx", true}, - {"/src/", true}, - {"/src1/", false}, - {"/src1/*filepath", true}, - {"/src2*filepath", true}, - {"/search/:query", false}, - {"/search/invalid", true}, - {"/user_:name", false}, - {"/user_x", true}, - {"/user_:name", false}, - {"/id:id", false}, - {"/id/:id", true}, - } - testRoutes(t, routes) -} - -func TestTreeChildConflict(t *testing.T) { - routes := []testRoute{ - {"/cmd/vet", false}, - {"/cmd/:tool/:sub", true}, - {"/src/AUTHORS", false}, - {"/src/*filepath", true}, - {"/user_x", false}, - {"/user_:name", true}, - {"/id/:id", false}, - {"/id:id", true}, - {"/:id", true}, - {"/*filepath", true}, - } - testRoutes(t, routes) -} - -func TestTreeDupliatePath(t *testing.T) { - tree := &node{} - - routes := [...]string{ - "/", - "/doc/", - "/src/*filepath", - "/search/:query", - "/user_:name", - } - for _, route := range routes { - recv := catchPanic(func() { - tree.addRoute(route, fakeHandler(route)) - }) - if recv != nil { - t.Fatalf("panic inserting route '%s': %v", route, recv) - } - - // Add again - recv = catchPanic(func() { - tree.addRoute(route, nil) - }) - if recv == nil { - t.Fatalf("no panic while inserting duplicate route '%s", route) - } - } - - //printChildren(tree, "") - - checkRequests(t, tree, testRequests{ - {"/", false, "/", nil}, - {"/doc/", false, "/doc/", nil}, - {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}}, - {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, - {"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}}, - }) -} - -func TestEmptyWildcardName(t *testing.T) { - tree := &node{} - - routes := [...]string{ - "/user:", - "/user:/", - "/cmd/:/", - "/src/*", - } - for _, route := range routes { - recv := catchPanic(func() { - tree.addRoute(route, nil) - }) - if recv == nil { - t.Fatalf("no panic while inserting route with empty wildcard name '%s", route) - } - } -} - -func TestTreeCatchAllConflict(t *testing.T) { - routes := []testRoute{ - {"/src/*filepath/x", true}, - {"/src2/", false}, - {"/src2/*filepath/x", true}, - } - testRoutes(t, routes) -} - -func TestTreeCatchAllConflictRoot(t *testing.T) { - routes := []testRoute{ - {"/", false}, - {"/*filepath", true}, - } - testRoutes(t, routes) -} - -/*func TestTreeDuplicateWildcard(t *testing.T) { - tree := &node{} - - routes := [...]string{ - "/:id/:name/:id", - } - for _, route := range routes { - ... - } -}*/ - -func TestTreeTrailingSlashRedirect(t *testing.T) { - tree := &node{} - - routes := [...]string{ - "/hi", - "/b/", - "/search/:query", - "/cmd/:tool/", - "/src/*filepath", - "/x", - "/x/y", - "/y/", - "/y/z", - "/0/:id", - "/0/:id/1", - "/1/:id/", - "/1/:id/2", - "/aa", - "/a/", - "/doc", - "/doc/go_faq.html", - "/doc/go1.html", - "/no/a", - "/no/b", - "/api/hello/:name", - } - for _, route := range routes { - recv := catchPanic(func() { - tree.addRoute(route, fakeHandler(route)) - }) - if recv != nil { - t.Fatalf("panic inserting route '%s': %v", route, recv) - } - } - - //printChildren(tree, "") - - tsrRoutes := [...]string{ - "/hi/", - "/b", - "/search/gopher/", - "/cmd/vet", - "/src", - "/x/", - "/y", - "/0/go/", - "/1/go", - "/a", - "/doc/", - } - for _, route := range tsrRoutes { - handler, _, tsr := tree.getValue(route) - if handler != nil { - t.Fatalf("non-nil handler for TSR route '%s", route) - } else if !tsr { - t.Errorf("expected TSR recommendation for route '%s'", route) - } - } - - noTsrRoutes := [...]string{ - "/", - "/no", - "/no/", - "/_", - "/_/", - "/api/world/abc", - } - for _, route := range noTsrRoutes { - handler, _, tsr := tree.getValue(route) - if handler != nil { - t.Fatalf("non-nil handler for No-TSR route '%s", route) - } else if tsr { - t.Errorf("expected no TSR recommendation for route '%s'", route) - } - } -} - -func TestTreeFindCaseInsensitivePath(t *testing.T) { - tree := &node{} - - routes := [...]string{ - "/hi", - "/b/", - "/ABC/", - "/search/:query", - "/cmd/:tool/", - "/src/*filepath", - "/x", - "/x/y", - "/y/", - "/y/z", - "/0/:id", - "/0/:id/1", - "/1/:id/", - "/1/:id/2", - "/aa", - "/a/", - "/doc", - "/doc/go_faq.html", - "/doc/go1.html", - "/doc/go/away", - "/no/a", - "/no/b", - } - - for _, route := range routes { - recv := catchPanic(func() { - tree.addRoute(route, fakeHandler(route)) - }) - if recv != nil { - t.Fatalf("panic inserting route '%s': %v", route, recv) - } - } - - // Check out == in for all registered routes - // With fixTrailingSlash = true - for _, route := range routes { - out, found := tree.findCaseInsensitivePath(route, true) - if !found { - t.Errorf("Route '%s' not found!", route) - } else if string(out) != route { - t.Errorf("Wrong result for route '%s': %s", route, string(out)) - } - } - // With fixTrailingSlash = false - for _, route := range routes { - out, found := tree.findCaseInsensitivePath(route, false) - if !found { - t.Errorf("Route '%s' not found!", route) - } else if string(out) != route { - t.Errorf("Wrong result for route '%s': %s", route, string(out)) - } - } - - tests := []struct { - in string - out string - found bool - slash bool - }{ - {"/HI", "/hi", true, false}, - {"/HI/", "/hi", true, true}, - {"/B", "/b/", true, true}, - {"/B/", "/b/", true, false}, - {"/abc", "/ABC/", true, true}, - {"/abc/", "/ABC/", true, false}, - {"/aBc", "/ABC/", true, true}, - {"/aBc/", "/ABC/", true, false}, - {"/abC", "/ABC/", true, true}, - {"/abC/", "/ABC/", true, false}, - {"/SEARCH/QUERY", "/search/QUERY", true, false}, - {"/SEARCH/QUERY/", "/search/QUERY", true, true}, - {"/CMD/TOOL/", "/cmd/TOOL/", true, false}, - {"/CMD/TOOL", "/cmd/TOOL/", true, true}, - {"/SRC/FILE/PATH", "/src/FILE/PATH", true, false}, - {"/x/Y", "/x/y", true, false}, - {"/x/Y/", "/x/y", true, true}, - {"/X/y", "/x/y", true, false}, - {"/X/y/", "/x/y", true, true}, - {"/X/Y", "/x/y", true, false}, - {"/X/Y/", "/x/y", true, true}, - {"/Y/", "/y/", true, false}, - {"/Y", "/y/", true, true}, - {"/Y/z", "/y/z", true, false}, - {"/Y/z/", "/y/z", true, true}, - {"/Y/Z", "/y/z", true, false}, - {"/Y/Z/", "/y/z", true, true}, - {"/y/Z", "/y/z", true, false}, - {"/y/Z/", "/y/z", true, true}, - {"/Aa", "/aa", true, false}, - {"/Aa/", "/aa", true, true}, - {"/AA", "/aa", true, false}, - {"/AA/", "/aa", true, true}, - {"/aA", "/aa", true, false}, - {"/aA/", "/aa", true, true}, - {"/A/", "/a/", true, false}, - {"/A", "/a/", true, true}, - {"/DOC", "/doc", true, false}, - {"/DOC/", "/doc", true, true}, - {"/NO", "", false, true}, - {"/DOC/GO", "", false, true}, - } - // With fixTrailingSlash = true - for _, test := range tests { - out, found := tree.findCaseInsensitivePath(test.in, true) - if found != test.found || (found && (string(out) != test.out)) { - t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t", - test.in, string(out), found, test.out, test.found) - return - } - } - // With fixTrailingSlash = false - for _, test := range tests { - out, found := tree.findCaseInsensitivePath(test.in, false) - if test.slash { - if found { // test needs a trailingSlash fix. It must not be found! - t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out)) - } - } else { - if found != test.found || (found && (string(out) != test.out)) { - t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t", - test.in, string(out), found, test.out, test.found) - return - } - } - } -} - -func TestTreeInvalidNodeType(t *testing.T) { - tree := &node{} - tree.addRoute("/", fakeHandler("/")) - tree.addRoute("/:page", fakeHandler("/:page")) - - // set invalid node type - tree.children[0].nType = 42 - - // normal lookup - recv := catchPanic(func() { - tree.getValue("/test") - }) - if rs, ok := recv.(string); !ok || rs != "Invalid node type" { - t.Fatalf(`Expected panic "Invalid node type", got "%v"`, recv) - } - - // case-insensitive lookup - recv = catchPanic(func() { - tree.findCaseInsensitivePath("/test", true) - }) - if rs, ok := recv.(string); !ok || rs != "Invalid node type" { - t.Fatalf(`Expected panic "Invalid node type", got "%v"`, recv) - } -} diff --git a/_third_party/github.com/korandiz/mpa/LICENSE.txt b/_third_party/github.com/korandiz/mpa/LICENSE.txt deleted file mode 100644 index 94a9ed0..0000000 --- a/_third_party/github.com/korandiz/mpa/LICENSE.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/_third_party/github.com/korandiz/mpa/README.txt b/_third_party/github.com/korandiz/mpa/README.txt deleted file mode 100644 index 1739d1b..0000000 --- a/_third_party/github.com/korandiz/mpa/README.txt +++ /dev/null @@ -1 +0,0 @@ -Please see http://godoc.org/github.com/korandiz/mpa diff --git a/_third_party/github.com/korandiz/mpa/bitreader_test.go b/_third_party/github.com/korandiz/mpa/bitreader_test.go deleted file mode 100644 index 9ce02e6..0000000 --- a/_third_party/github.com/korandiz/mpa/bitreader_test.go +++ /dev/null @@ -1,252 +0,0 @@ -// mpa, an MPEG-1 Audio library -// Copyright (C) 2014 KORÁNDI Zoltán -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License, version 3 as -// published by the Free Software Foundation. -// -// 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// Please note that, being hungarian, my last name comes before my first -// name. That's why it's in all caps, and not because I like to shout my -// name. So please don't start your emails with "Hi Korandi" or "Dear Mr. -// Zoltan", because it annoys the hell out of me. Thanks. - -package mpa - -import ( - "bytes" - "testing" -) - -func TestBitreader_readBits(t *testing.T) { - testData := []byte{ - 0x28, 0xc8, 0x28, 0x60, 0x70, 0x40, 0x12, 0x02, - 0x80, 0x2c, 0x01, 0x80, 0x06, 0x80, 0x0e, 0x00, - 0x0f, - } - - in := []byte(nil) - for i := 0; i < 4096; i++ { - in = append(in, testData...) - } - r := &bitReader{input: bytes.NewReader(in)} - - for i := 0; i < 1234; i++ { - d, err := r.readBits(0) - if err != nil { - t.Error("Unexpected error:", err) - return - } - - if d != 0 { - t.Errorf("Read 0 bits, got %d. (i = %d)", d, i) - return - } - } - - for i := 0; i < 4096; i++ { - for j := 0; j < 16; j++ { - d, err := r.readBits(j + 1) - - if err != nil { - t.Error("Unexpected error:", err) - return - } - - if d != j { - t.Errorf("i = %d, j = %d, read %d.", i, j, d) - return - } - } - } - - if _, err := r.readBits(1); err == nil { - t.Error("Read after EOF returned nil error.") - } -} - -func TestBitreader_readByte(t *testing.T) { - testData := []byte{ - 0xaa, 0x01, 0xaa, 0x02, 0xaa, 0x03, 0xaa, 0x04, - 0xaa, 0x05, 0xaa, 0x06, 0xaa, 0x07, 0xaa, 0x08, - 0xaa, 0xaa, 0x09, 0xaa, 0xaa, 0x0a, - 0xaa, 0xaa, 0x0b, 0xaa, 0xaa, 0x0c, - 0xaa, 0xaa, 0x0d, 0xaa, 0xaa, 0x0e, - 0xaa, 0xaa, 0x0f, 0xaa, 0xaa, 0x10, - 0xaa, 0xaa, 0xaa, - } - - in := []byte(nil) - for i := 0; i < 4096; i++ { - in = append(in, testData...) - } - r := &bitReader{input: bytes.NewReader(in)} - - for i := 0; i < 4096; i++ { - for j := 1; j <= 16; j++ { - if _, err := r.readBits(j); err != nil { - t.Error("Unexpected error:", err) - return - } - - d, err := r.readByte() - if err != nil { - t.Error("Unexpected error:", err) - return - } - - if d != j { - t.Errorf("i = %d, j = %d, read %d.", i, j, d) - return - } - } - - for i := 0; i < 3; i++ { - d, err := r.readByte() - if err != nil { - t.Error("Unexpected error:", err) - return - } - - if d != 0xaa { - t.Errorf("i = %d, read %d.", i, d) - return - } - } - } - - if _, err := r.readByte(); err == nil { - t.Error("Read after EOF returned nil error.") - } -} - -func TestBitreader_readBytes(t *testing.T) { - testData := []byte(nil) - for i := 0; i < 8; i++ { - for j := 0; j <= 1440; j++ { // 1440 == max. frame size - if i > 0 { - testData = append(testData, 0) - } - for k := 0; k < j; k++ { - testData = append(testData, byte(len(testData))) - } - } - } - r := &bitReader{input: bytes.NewReader(testData)} - - buff, b := make([]byte, 1440), byte(0) - for i := 0; i < 8; i++ { - for j := 0; j <= 1440; j++ { - if _, err := r.readBits(i); err != nil { - t.Error("Unexpected error:", err) - return - } - - n, err := r.readBytes(buff[0:j]) - if err != nil { - t.Error("Unexpected error:", err) - return - } - - if n != j { - t.Errorf("Tryed to read %d bytes, read %d.", j, n) - return - } - - if i > 0 { - b++ - } - - for k := range buff[0:n] { - if buff[k] != b { - t.Errorf("i = %d, j = %d, k = %d, exptected %d, found %d.", - i, j, k, b, buff[k]) - return - } - b++ - } - } - } - - if _, err := r.readBytes(buff[0:1]); err == nil { - t.Error("Read after EOF returned nil error.") - } -} - -func TestBitreader_syncword3(t *testing.T) { - testData := []byte{ - 0x7f, 0xfd, 0x00, - 0x3f, 0xfe, 0x80, - 0x1f, 0xff, 0x40, - 0x0f, 0xff, 0xa0, - 0x07, 0xff, 0xd0, - 0x03, 0xff, 0xe8, - 0x01, 0xff, 0xf4, - 0x00, 0xff, 0xfa, - 0x00, 0x7f, 0xfd, - } - - in := []byte(nil) - for i := 0; i < 4096; i++ { - in = append(in, testData...) - } - - r := &bitReader{input: bytes.NewReader(in)} - for i := 0; i < 4096; i++ { - for j := range testData { - sw := r.syncword3() - - if j == 22 && !sw { - t.Errorf("i = %d, j = %d, syncword3() returned false.", i, j) - return - } - - if j != 22 && sw { - t.Errorf("i = %d, j = %d, syncword3() returned true.", i, j) - return - } - - if _, err := r.readByte(); err != nil { - t.Error("Unexpected error:", err) - return - } - } - } -} - -func TestBitreader_lookahead(t *testing.T) { - in := make([]byte, 8192) - for i := 0; i < 100; i++ { - in[5000+i] = byte(i) - } - r := &bitReader{input: bytes.NewReader(in)} - - for i := 0; i < 1000; i++ { - r.readByte() - } - r.readBits(3) - - for i := 0; i < 100; i++ { - val1 := uint32(i<<24 | (i+1)<<16 | (i+2)<<8 | (i + 3)) - ok1 := 3999+i+3 < len(r.buffer) - - val2, ok2 := r.lookahead(3999 + i) - - if ok1 != ok2 { - t.Errorf("i = %d, expected %t, got %t", i, ok1, ok2) - return - } - - if ok1 && val1 != val2 { - t.Errorf("i = %d, expected %.8x, got %.8x", i, val1, val2) - return - } - } -} diff --git a/_third_party/github.com/korandiz/mpa/dct_test.go b/_third_party/github.com/korandiz/mpa/dct_test.go deleted file mode 100644 index 3642b11..0000000 --- a/_third_party/github.com/korandiz/mpa/dct_test.go +++ /dev/null @@ -1,118 +0,0 @@ -// mpa, an MPEG-1 Audio library -// Copyright (C) 2014 KORÁNDI Zoltán -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License, version 3 as -// published by the Free Software Foundation. -// -// 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// Please note that, being hungarian, my last name comes before my first -// name. That's why it's in all caps, and not because I like to shout my -// name. So please don't start your emails with "Hi Korandi" or "Dear Mr. -// Zoltan", because it annoys the hell out of me. Thanks. - -package mpa - -import ( - "math" - "math/rand" - "testing" -) - -func TestDct(t *testing.T) { - testDct(t, new(dct2Test)) - testDct(t, new(dct4Test)) - testDct(t, new(dct8Test)) - testDct(t, new(dct16Test)) - testDct(t, new(dct32Test)) -} - -func testDct(t *testing.T, dt dctTester) { - rand.Seed(42) // make it repeatable - max, x := 0.0, dt.slice() - for i := 0; i < 1000; i++ { - for i := range x { - x[i] = 2*rand.Float32() - 1 - } - X := directDct(x) - dt.transform() - for i := range x { - max = math.Max(max, math.Abs(X[i]-float64(x[i]))) - } - } - - t.Logf("N = %d, max. difference = %e", len(x), max) - if max >= 1.0/(1<<16) { - t.Fail() - } -} - -func directDct(x []float32) []float64 { - N, NFl := len(x), float64(len(x)) - X := make([]float64, N) - for n := 0; n < N; n++ { - nFl := float64(n) - for k := 0; k < N; k++ { - kFl := float64(k) - X[n] += float64(x[k]) * math.Cos(math.Pi*(2*kFl+1)*nFl/(2*NFl)) - } - } - return X -} - -type dctTester interface { - slice() []float32 - transform() -} - -type dct2Test [2]float32 - -func (d *dct2Test) slice() []float32 { - return d[:] -} -func (d *dct2Test) transform() { - dct2((*[2]float32)(d)) -} - -type dct4Test [4]float32 - -func (d *dct4Test) slice() []float32 { - return d[:] -} -func (d *dct4Test) transform() { - dct4((*[4]float32)(d)) -} - -type dct8Test [8]float32 - -func (d *dct8Test) slice() []float32 { - return d[:] -} -func (d *dct8Test) transform() { - dct8((*[8]float32)(d)) -} - -type dct16Test [16]float32 - -func (d *dct16Test) slice() []float32 { - return d[:] -} -func (d *dct16Test) transform() { - dct16((*[16]float32)(d)) -} - -type dct32Test [32]float32 - -func (d *dct32Test) slice() []float32 { - return d[:] -} -func (d *dct32Test) transform() { - dct32(d[:]) -} diff --git a/_third_party/github.com/korandiz/mpa/decoder_test.go b/_third_party/github.com/korandiz/mpa/decoder_test.go deleted file mode 100644 index c184f25..0000000 --- a/_third_party/github.com/korandiz/mpa/decoder_test.go +++ /dev/null @@ -1,423 +0,0 @@ -// mpa, an MPEG-1 Audio library -// Copyright (C) 2014 KORÁNDI Zoltán -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License, version 3 as -// published by the Free Software Foundation. -// -// 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// Please note that, being hungarian, my last name comes before my first -// name. That's why it's in all caps, and not because I like to shout my -// name. So please don't start your emails with "Hi Korandi" or "Dear Mr. -// Zoltan", because it annoys the hell out of me. Thanks. - -package mpa - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "math" - "math/rand" - "os" - "testing" -) - -var PS = string(os.PathSeparator) - -func TestDecoder_Compliance1(t *testing.T) { - testCompliance12(t, "fl1", 2, 18816, 4) - testCompliance12(t, "fl2", 2, 18816, 4) - testCompliance12(t, "fl3", 2, 18816, 4) - testCompliance12(t, "fl4", 1, 18816, 4) - testCompliance12(t, "fl5", 2, 18816, 4) - testCompliance12(t, "fl6", 2, 18816, 4) - testCompliance12(t, "fl7", 2, 24192, 4) - testCompliance12(t, "fl8", 2, 18816, 4) -} - -func TestDecoder_Compliance2(t *testing.T) { - testCompliance12(t, "fl10", 2, 56448, 1) - testCompliance12(t, "fl11", 2, 56448, 1) - testCompliance12(t, "fl12", 2, 56448, 1) - testCompliance12(t, "fl13", 1, 56448, 1) - testCompliance12(t, "fl14", 2, 18432, 1) - testCompliance12(t, "fl15", 2, 18432, 1) - testCompliance12(t, "fl16", 2, 72576, 1) -} - -func TestDecoder_Compliance3(t *testing.T) { - testCompliance3(t, "compl", 1, 248832) - testCompliance3(t, "hecommon", 2, 33408) - testCompliance3(t, "he_32khz", 1, 171648) - testCompliance3(t, "he_44khz", 1, 471168) - testCompliance3(t, "he_48khz", 1, 171648) - testCompliance3(t, "he_free", 2, 77184) - testCompliance3_heMode(t) - testCompliance3(t, "si", 1, 134784) - testCompliance3(t, "si_block", 1, 72576) - testCompliance3(t, "si_huff", 1, 85248) - testCompliance3(t, "sin1k0db", 2, 362880) -} - -func testCompliance12(t *testing.T, fn string, nch int, n, k int) { - t.Log("==", fn, "==") - - in, err := loadHexFile(fn+".mpg", k) - if err != nil { - t.Log(" ", err) - return - } - - ref, err := loadPcmFile(fn+".pcm", nch) - if err != nil { - t.Log(" ", err) - return - } - - test, err := decodeTestFile(in, nch) - if err != nil { - t.Error(" ", err) - return - } - - testRms(t, ref, test, nch, n, 1) -} - -func testCompliance3(t *testing.T, fn string, nch, n int) { - t.Log("==", fn, "==") - - in, err := ioutil.ReadFile("iso11172-4" + PS + fn + ".bit") - if err != nil { - t.Log(" ", err) - return - } - - ref, err := loadPcmFile(fn+".hex", nch) - if err != nil { - t.Log(" ", err) - return - } - - test, err := decodeTestFile(in, nch) - if err != nil { - t.Error(" ", err) - return - } - - testRms(t, ref, test, nch, n, 1) -} - -func testCompliance3_heMode(t *testing.T) { - // he_mode requires special treatment, because it contains both stereo and - // mono frames. - - t.Log("== he_mode ==") - - in, err := ioutil.ReadFile("iso11172-4" + PS + "he_mode.bit") - if err != nil { - t.Log(" ", err) - return - } - - ref0, err := loadPcmFile("he_mode.hex", 1) - if err != nil { - t.Log(" ", err) - return - } - - // Frames 1-10 and 111-127 are in mono. Fix that. - n := 146304 - ref := [2][]int32{make([]int32, n), make([]int32, n)} - for frame, s, d := 1, 0, 0; frame <= 127; frame++ { - if frame <= 10 || frame >= 111 && frame <= 127 { - for t := 0; t < 1152; t++ { - ref[0][d] = ref0[0][s] - ref[1][d] = ref0[0][s] - d++ - s++ - } - } else { - for t := 0; t < 1152; t++ { - ref[0][d] = ref0[0][s] - ref[1][d] = ref0[0][s+1] - d++ - s += 2 - } - } - } - - test, err := decodeTestFile(in, 2) - if err != nil { - t.Error(" ", err) - return - } - - // The reference output for Layer III test streams (except for compl.hex) - // was generated with l3dec which seems to do no alias reduction on the - // lower subbands of granules with mixed blocks. To prevent the test from - // failing, we set an error margin 6x greater than normal. - testRms(t, ref, test, 2, n, 6) -} - -func loadHexFile(fn string, k int) ([]byte, error) { - file, err := os.Open("iso11172-4" + PS + fn) - if err != nil { - return nil, err - } - defer file.Close() - data := []byte(nil) - for { - var x int - n, err := fmt.Fscanf(file, "%X\n", &x) - if n != 0 { - if k == 4 { - data = append(data, byte((x>>24)&0xff)) - data = append(data, byte((x>>16)&0xff)) - data = append(data, byte((x>>8)&0xff)) - } - data = append(data, byte(x&0xff)) - } - if err == io.EOF { - return data, nil - } else if err != nil { - return nil, err - } - } -} - -func loadPcmFile(fn string, nch int) ([2][]int32, error) { - data := [2][]int32{} - file, err := os.Open("iso11172-4" + PS + fn) - if err != nil { - return data, err - } - defer file.Close() - for { - for ch := 0; ch < nch; ch++ { - var x int32 - n, err := fmt.Fscanf(file, "%X\n", &x) - if n != 0 { - data[ch] = append(data[ch], x) - } - if err == io.EOF { - return data, nil - } else if err != nil { - return data, err - } - } - } - -} - -func decodeTestFile(in []byte, nch int) ([2][]int32, error) { - d := &Decoder{Input: bytes.NewReader(in)} - tmp := make([]float32, 1152) - data := [2][]int32{} - for { - if err := d.DecodeFrame(); err != nil { - if _, ms := err.(MalformedStream); ms { - continue - } else if err == io.EOF || err == io.ErrUnexpectedEOF { - return data, nil - } else { - return data, err - } - } - N := d.NSamples() - for ch := 0; ch < nch; ch++ { - d.ReadSamples(ch, tmp) - for t := 0; t < N; t++ { - x := int32((tmp[t]+1)*(1<<23)) ^ (1 << 23) - if x == 1<<24 { - x = (1 << 24) - 1 - } - data[ch] = append(data[ch], x) - } - } - } -} - -func testRms(t *testing.T, ref, test [2][]int32, nch, n int, k float64) { - maxRms := 1 / (math.Pow(2, 15) * math.Sqrt(12)) - maxMax := math.Pow(2, -14) - - if len(test[0]) < n { - t.Errorf(" # of samples/channel: %d, expected: %d", len(test[0]), n) - t.Log(" [FAIL]") - return - } - - fail := false - for ch := 0; ch < nch; ch++ { - rms, max := 0.0, 0.0 - for t := 0; t < n; t++ { - s1 := float64(ref[ch][t]^(1<<23))/(1<<23) - 1 - s2 := float64(test[ch][t]^(1<<23))/(1<<23) - 1 - d := s1 - s2 - if d < 0 { - d *= -1 - } - if d > max { - max = d - } - rms += d * d - } - rms = math.Sqrt(rms / float64(n)) - rms /= maxRms - max /= maxMax - t.Logf(" Channel %d: rms=%f, max=%f", ch, rms, max) - if rms >= k || max > k { - t.Fail() - fail = true - } - } - if fail { - t.Error(" [FAIL]") - } -} - -func TestDecoder_errorTolerance(t *testing.T) { - // This test never explicitly fails. If it doesn't panic, it's okay. - - var files = []string{ - // Layer I - "fl1.mpg", - "fl2.mpg", - "fl3.mpg", - "fl4.mpg", - "fl5.mpg", - "fl6.mpg", - "fl7.mpg", - "fl8.mpg", - "fl10.mpg", - "fl11.mpg", - "fl12.mpg", - "fl13.mpg", - "fl14.mpg", - "fl15.mpg", - "fl16.mpg", - "compl.bit", - "he_32khz.bit", - "he_44khz.bit", - "he_48khz.bit", - "he_free.bit", - "he_mode.bit", - "hecommon.bit", - "si.bit", - "si_block.bit", - "si_huff.bit", - "sin1k0db.bit", - } - n := 100 - if testing.Short() { - n = 1 - } - for _, fn := range files { - fuzz(t, fn, n, 1e-4) - } -} - -func fuzz(t *testing.T, fn string, n int, p float64) { - rand.Seed(42) - in1, err := ioutil.ReadFile("iso11172-4" + PS + fn) - if err != nil { - t.Logf("Could not read file %s: %s", fn, err) - return - } - in := []byte(nil) - for i := 0; i < n; i++ { - in = append(in, in1...) - } - d := Decoder{Input: &fuzzer{bytes.NewReader(in), p}} - tmp := make([]float32, 2000) - - for { - err := d.DecodeFrame() - if _, ms := err.(MalformedStream); err != nil && !ms { - break - } - d.Bitrate() - d.Copyrighted() - d.Emphasis() - d.Layer() - d.Mode() - d.NChannels() - d.NSamples() - d.Original() - d.SamplingFrequency() - d.ReadSamples(ChLeft, tmp) - d.ReadSamples(ChRight, tmp) - } -} - -type fuzzer struct { - r io.Reader - p float64 -} - -func (f *fuzzer) Read(p []byte) (int, error) { - n, err := f.r.Read(p) - for i := 0; i < n; i++ { - if rand.Float64() < 8*f.p { - p[i] ^= 1 << uint(rand.Intn(8)) - } - } - return n, err -} - -func TestDecoder_junkTolerance(t *testing.T) { - // This test never explicitly fails. If it doesn't panic, it's okay. - - N := 1 << 30 - if testing.Short() { - N = 1 << 23 - } - rand.Seed(42) - d := &Decoder{Input: &junkReader{N}} - tmp := make([]float32, 2000) - for n := 0; ; n++ { - err := d.DecodeFrame() - if _, ms := err.(MalformedStream); err != nil && !ms { - t.Logf("Decoder.DecodeFrame called %d times.", n) - break - } - d.Bitrate() - d.Copyrighted() - d.Emphasis() - d.Layer() - d.Mode() - d.NChannels() - d.NSamples() - d.Original() - d.SamplingFrequency() - d.ReadSamples(ChLeft, tmp) - d.ReadSamples(ChRight, tmp) - } -} - -type junkReader struct { - n int -} - -func (r *junkReader) Read(p []byte) (int, error) { - n := 0 - for n < len(p) && n < r.n { - p[n] = byte(rand.Int()) - n++ - } - r.n -= n - if n < len(p) { - return n, io.EOF - } - - return n, nil -} diff --git a/_third_party/github.com/korandiz/mpa/imdct_test.go b/_third_party/github.com/korandiz/mpa/imdct_test.go deleted file mode 100644 index eaee764..0000000 --- a/_third_party/github.com/korandiz/mpa/imdct_test.go +++ /dev/null @@ -1,116 +0,0 @@ -// mpa, an MPEG-1 Audio library -// Copyright (C) 2014 KORÁNDI Zoltán -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License, version 3 as -// published by the Free Software Foundation. -// -// 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// Please note that, being hungarian, my last name comes before my first -// name. That's why it's in all caps, and not because I like to shout my -// name. So please don't start your emails with "Hi Korandi" or "Dear Mr. -// Zoltan", because it annoys the hell out of me. Thanks. - -package mpa - -import ( - "math" - "math/rand" - "testing" -) - -func TestImdct(t *testing.T) { - testImdct(t, new(imdct4Test)) - testImdct(t, new(imdct12Test)) - testImdct(t, new(imdct36Test)) -} - -func testImdct(t *testing.T, it imdctTester) { - rand.Seed(42) // make it repeatable - X := it.input() - N := 2 * len(X) - x1 := make([]float64, N) - max := 0.0 - for i := 0; i < 1000; i++ { - for k := range X { - X[k] = 2*rand.Float32() - 1 - } - directImdct(X, x1) - x2 := it.transform() - for k := range x1 { - max = math.Max(max, math.Abs(x1[k]-float64(x2[k]))) - } - } - - t.Logf("N = %d, max. difference = %e", N, max) - if max >= 1.0/(1<<16) { - t.Fail() - } -} - -func directImdct(in []float32, out []float64) { - N, Nf := 2*len(in), float64(2*len(in)) - for n := 0; n < N; n++ { - nf := float64(n) - out[n] = 0 - for k := 0; k < N/2; k++ { - kf := float64(k) - in64 := float64(in[k]) - out[n] += in64 * math.Cos(math.Pi/(2*Nf)*(2*nf+1+Nf/2)*(2*kf+1)) - } - } -} - -type imdctTester interface { - input() []float32 - transform() []float32 -} - -type imdct4Test struct { - in [2]float32 - out [4]float32 -} - -func (i *imdct4Test) input() []float32 { - return i.in[:] -} - -func (i *imdct4Test) transform() []float32 { - imdct4(&i.in, &i.out) - return i.out[:] -} - -type imdct12Test struct { - in [6]float32 - out [12]float32 -} - -func (i *imdct12Test) input() []float32 { - return i.in[:] -} - -func (i *imdct12Test) transform() []float32 { - imdct12(i.in[:], i.out[:]) - return i.out[:] -} - -type imdct36Test struct { - in [18]float32 - out [36]float32 -} - -func (i *imdct36Test) input() []float32 { - return i.in[:] -} - -func (i *imdct36Test) transform() []float32 { - imdct36(i.in[:], i.out[:]) - return i.out[:] -} diff --git a/_third_party/github.com/korandiz/mpa/imdctfilter_test.go b/_third_party/github.com/korandiz/mpa/imdctfilter_test.go deleted file mode 100644 index 91b53da..0000000 --- a/_third_party/github.com/korandiz/mpa/imdctfilter_test.go +++ /dev/null @@ -1,142 +0,0 @@ -// mpa, an MPEG-1 Audio library -// Copyright (C) 2014 KORÁNDI Zoltán -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License, version 3 as -// published by the Free Software Foundation. -// -// 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// Please note that, being hungarian, my last name comes before my first -// name. That's why it's in all caps, and not because I like to shout my -// name. So please don't start your emails with "Hi Korandi" or "Dear Mr. -// Zoltan", because it annoys the hell out of me. Thanks. - -package mpa - -import ( - "math" - "math/rand" - "testing" -) - -func TestImdctFilter(t *testing.T) { - for typ := 0; typ <= 3; typ++ { - in1, out1 := make([]float32, 18), make([]float32, 18) - in2, out2 := make([]float64, 18), make([]float64, 18) - f1, f2 := imdctFilter{}, directImdctFilter{} - rand.Seed(42) - max := 0.0 - for i := 0; i < 1000; i++ { - for j := 0; j < 18; j++ { - x := 2*rand.Float64() - 1 - in1[j] = float32(x) - in2[j] = x - } - f1.filter(in1, out1, typ) - f2.filter(in2, out2, typ) - for j := 0; j < 18; j++ { - max = math.Max(max, math.Abs(float64(out1[j])-out2[j])) - } - } - t.Logf("Type = %d, max. difference = %e", typ, max) - if max >= 1.0/(1<<16) { - t.Fail() - } - } -} - -type directImdctFilter struct { - prev, curr [36]float64 -} - -func (f *directImdctFilter) filter(X []float64, x []float64, typ int) { - if typ != 2 { - tmp := make([]float64, 36) - for i := 0; i < 36; i++ { - iF := float64(i) - for k := 0; k < 18; k++ { - kF := float64(k) - tmp[i] += X[k] * math.Cos(math.Pi/72*(2*iF+19)*(2*kF+1)) - } - var w float64 - switch typ { - case 0: - w = math.Sin(math.Pi / 36 * (iF + 0.5)) - case 1: - switch { - case i <= 17: - w = math.Sin(math.Pi / 36 * (iF + 0.5)) - case i <= 23: - w = 1 - case i <= 29: - w = math.Sin(math.Pi / 12 * (iF - 17.5)) - default: - w = 0 - } - case 3: - switch { - case i <= 5: - w = 0 - case i <= 11: - w = math.Sin(math.Pi / 12 * (iF - 5.5)) - case i <= 17: - w = 1 - default: - w = math.Sin(math.Pi / 36 * (iF + 0.5)) - } - } - f.curr[i] = w * tmp[i] - } - } else { - var ( - Y [3][]float64 - y [3][]float64 - ) - for w := 0; w < 3; w++ { - Y[w] = make([]float64, 18) - y[w] = make([]float64, 36) - for i := 0; i < 6; i++ { - Y[w][i] = X[3*i+w] - } - } - - for w := 0; w < 3; w++ { - for i := 0; i < 12; i++ { - iF := float64(i) - for k := 0; k < 6; k++ { - kF := float64(k) - y[w][i] += Y[w][k] * math.Cos(math.Pi/24*(2*iF+7)*(2*kF+1)) - } - y[w][i] *= math.Sin(math.Pi / 12 * (float64(i) + 0.5)) - } - } - for i := 0; i < 36; i++ { - switch { - case i <= 5: - f.curr[i] = 0 - case i <= 11: - f.curr[i] = y[0][i-6] - case i <= 17: - f.curr[i] = y[0][i-6] + y[1][i-12] - case i <= 23: - f.curr[i] = y[1][i-12] + y[2][i-18] - case i <= 29: - f.curr[i] = y[2][i-18] - default: - f.curr[i] = 0 - } - } - } - - for i := 0; i < 18; i++ { - x[i] = f.prev[i+18] + f.curr[i] - } - f.prev = f.curr -} diff --git a/_third_party/github.com/korandiz/mpa/reservoirreader_test.go b/_third_party/github.com/korandiz/mpa/reservoirreader_test.go deleted file mode 100644 index bf60dca..0000000 --- a/_third_party/github.com/korandiz/mpa/reservoirreader_test.go +++ /dev/null @@ -1,204 +0,0 @@ -// mpa, an MPEG-1 Audio library -// Copyright (C) 2014 KORÁNDI Zoltán -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License, version 3 as -// published by the Free Software Foundation. -// -// 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// Please note that, being hungarian, my last name comes before my first -// name. That's why it's in all caps, and not because I like to shout my -// name. So please don't start your emails with "Hi Korandi" or "Dear Mr. -// Zoltan", because it annoys the hell out of me. Thanks. - -package mpa - -import ( - "bytes" - "testing" -) - -func TestReservoir_setSize_load(t *testing.T) { - in := []byte(nil) - for i := 0; i <= 965; i++ { - for j := i; j < 1930; j++ { - in = append(in, 1-byte(i%2)) - } - } - br := &bitReader{input: bytes.NewReader(in)} - res := &reservoirReader{stream: br} - - for i := 0; i <= 965; i++ { - if err := res.setSize(i); err != nil { - t.Error("setSize failed:", err) - return - } - if err := res.load(1930 - i); err != nil { - t.Error("load failed:", err) - return - } - for j := 0; j < i; j++ { - d, err := res.readBits(8) - if err != nil { - t.Error("readBits(8) failed:", err) - return - } - if d != i%2 { - t.Errorf("i = %d, j = %d, expected %d, read %d", i, j, i%2, d) - return - } - } - } -} - -func TestReservoir_loadUntilSyncword(t *testing.T) { - in := []byte(nil) - for i := 1; i <= 254; i++ { - for j := 0; j < i; j++ { - in = append(in, byte(i)) - } - in = append(in, 0xff, 0xfa) - } - br := &bitReader{input: bytes.NewReader(in)} - res := &reservoirReader{stream: br} - - for i := 1; i <= 254; i++ { - if err := res.setSize(0); err != nil { - t.Error("setSize(0) failed:", err) - return - } - if err := res.loadUntilSyncword(); err != nil { - t.Error("loadUntilSyncword failed:", err) - return - } - for j := 0; j < i; j++ { - d, err := res.readBits(8) - if err != nil { - t.Error("reservoirReader.readBits(8) failed:", err) - return - } - if d != i { - t.Errorf("i = %d, j = %d, read %d", i, j, d) - return - } - } - if _, err := br.readBits(16); err != nil { - t.Error("bitReader.readBits(16) failed:", err) - return - } - } -} - -func TestReservoir_readBits(t *testing.T) { - testData := []byte{ - 0x28, 0xc8, 0x28, 0x60, 0x70, 0x40, 0x12, 0x02, - 0x80, 0x2c, 0x01, 0x80, 0x06, 0x80, 0x0e, 0x00, - 0x0f, - } - - in := []byte(nil) - for i := 0; i < 100; i++ { - in = append(in, testData...) - } - br := &bitReader{input: bytes.NewReader(in)} - res := &reservoirReader{stream: br} - res.load(len(in)) - - for i := 0; i < 1234; i++ { - d, err := res.readBits(0) - if err != nil { - t.Error("Unexpected error:", err) - return - } - - if d != 0 { - t.Errorf("Read 0 bits, got %d. (i = %d)", d, i) - return - } - } - - for i := 0; i < 100; i++ { - for j := 0; j < 16; j++ { - d, err := res.readBits(j + 1) - - if err != nil { - t.Error("Unexpected error:", err) - return - } - - if d != j { - t.Errorf("i = %d, j = %d, read %d.", i, j, d) - return - } - } - } - - if _, err := res.readBits(1); err == nil { - t.Error("Read after EOF returned nil error.") - } -} - -func TestReservoir_readCode(t *testing.T) { - tree := huffmanTree{ - 1 * 2, 2 * 2, - 0, 0, - 3 * 2, 4 * 2, - 0, 1, - 5 * 2, 6 * 2, - 0, 2, - 7 * 2, 8 * 2, - 0, 3, - 9 * 2, 10 * 2, - 0, 4, - 11 * 2, 12 * 2, - 0, 5, - 13 * 2, 14 * 2, - 0, 6, - 15 * 2, 16 * 2, - 0, 7, - 17 * 2, 18 * 2, - 0, 8, - 0, 9, - 0, 10, - } - testData := []byte{ - 0x5b, 0xbd, 0xf7, 0xef, 0xef, 0xf7, 0xfd, 0x6e, - 0xf7, 0xdf, 0xbf, 0xbf, 0xdf, 0xf5, 0xbb, 0xdf, - 0x7e, 0xfe, 0xff, 0x7f, 0xd6, 0xef, 0x7d, 0xfb, - 0xfb, 0xfd, 0xff, - } - - in := []byte(nil) - for i := 0; i < 35; i++ { - in = append(in, testData...) - } - br := &bitReader{input: bytes.NewReader(in)} - res := &reservoirReader{stream: br} - res.load(len(in)) - - for i := 0; i < 4*35; i++ { - for j := 0; j < 10; j++ { - d, err := res.readCode(tree) - if err != nil { - t.Error("Unexpected error:", err) - return - } - - if d != j { - t.Errorf("i = %d, j = %d, read %d.", i, j, d) - return - } - } - } - - if _, err := res.readCode(tree); err == nil { - t.Error("Read after EOF returned nil error.") - } -} diff --git a/_third_party/github.com/korandiz/mpa/synthesisfilter_test.go b/_third_party/github.com/korandiz/mpa/synthesisfilter_test.go deleted file mode 100644 index f8d23cf..0000000 --- a/_third_party/github.com/korandiz/mpa/synthesisfilter_test.go +++ /dev/null @@ -1,92 +0,0 @@ -// mpa, an MPEG-1 Audio library -// Copyright (C) 2014 KORÁNDI Zoltán -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License, version 3 as -// published by the Free Software Foundation. -// -// 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// Please note that, being hungarian, my last name comes before my first -// name. That's why it's in all caps, and not because I like to shout my -// name. So please don't start your emails with "Hi Korandi" or "Dear -// Mr. Zoltan", because it annoys the hell out of me. Thanks. - -package mpa - -import ( - "math" - "math/rand" - "testing" -) - -func TestSynthesisFilter(t *testing.T) { - rand.Seed(42) // make it repeatable - var ( - f1 directSynthesisFilter - in1 [32]float64 - out1 [32]float64 - f2 synthesisFilter - x2 [32]float32 - max float64 - ) - for i := 0; i < 2048; i++ { - for j := 0; j < 32; j++ { - in1[j] = 2*rand.Float64() - 1 - x2[j] = float32(in1[j]) - } - f1.filter(&in1, &out1) - f2.filter(x2[:]) - for j := 0; j < 32; j++ { - max = math.Max(max, math.Abs(out1[j]-float64(x2[j]))) - } - } - - t.Logf("max. difference = %e", max) - if max >= 1.0/(1<<16) { - t.Fail() - } -} - -type directSynthesisFilter [1024]float64 - -func (v *directSynthesisFilter) filter(in, out *[32]float64) { - for i := 1023; i >= 64; i-- { - v[i] = v[i-64] - } - - for i := 0; i <= 63; i++ { - v[i] = 0 - iFl := float64(i) - for k := 0; k <= 31; k++ { - kFl := float64(k) - v[i] += math.Cos((16+iFl)*(2*kFl+1)*math.Pi/64.0) * float64(in[k]) - } - } - - var u [512]float64 - for i := 0; i <= 7; i++ { - for j := 0; j <= 31; j++ { - u[i*64+j] = v[i*128+j] - u[i*64+32+j] = v[i*128+96+j] - } - } - - var w [512]float64 - for i := 0; i <= 511; i++ { - w[i] = u[i] * float64(synthesisWindow[i]) - } - - for j := 0; j <= 31; j++ { - out[j] = 0 - for i := 0; i <= 15; i++ { - out[j] += w[j+32*i] - } - } -} diff --git a/_third_party/github.com/mesilliac/pulse-simple/LICENSE b/_third_party/github.com/mesilliac/pulse-simple/LICENSE deleted file mode 100644 index a64eb30..0000000 --- a/_third_party/github.com/mesilliac/pulse-simple/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Thomas Iorns - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/_third_party/github.com/mesilliac/pulse-simple/README.md b/_third_party/github.com/mesilliac/pulse-simple/README.md deleted file mode 100644 index 9735ae5..0000000 --- a/_third_party/github.com/mesilliac/pulse-simple/README.md +++ /dev/null @@ -1,47 +0,0 @@ -pulse-simple -============ - -Cgo bindings to PulseAudio's Simple API, -for easily playing or capturing raw audio. - -The full Simple API is supported, -including channel mapping and setting buffer attributes. - -usage ------ - -Basic usage is to request a playback or capture stream, -then write bytes to or read bytes from it. - -Reading and writing will block until the given byte slice -is completely consumed or filled, or an error occurs. - -The format of the data will be as requested on stream creation. - -For example, -assuming "data" contains raw bytes representing stereophonic audio -in little-endian 16-bit integer PCM format, -the following will obtain a playback stream -and play the given data as audio on the default sound device. - - ss := pulse.SampleSpec{pulse.SAMPLE_S16LE, 44100, 2} - stream, _ := pulse.Playback("my app", "my stream", &ss) - defer stream.Free() - defer stream.Drain() - stream.Write(data) - -More example usage can be found in the examples folder. - -For more information, see the PulseAudio Simple API documentation at -http://www.freedesktop.org/software/pulseaudio/doxygen/simple.html - -license -------- - -MIT (see the included LICENSE file for full license text) - -authors -------- - -Thomas Iorns - diff --git a/_third_party/github.com/mjibson/nsf/nsf.go b/_third_party/github.com/mjibson/nsf/nsf.go index 86ac9d2..a1d50ea 100644 --- a/_third_party/github.com/mjibson/nsf/nsf.go +++ b/_third_party/github.com/mjibson/nsf/nsf.go @@ -60,7 +60,6 @@ func ReadNSF(b []byte) (*NSF, error) { n.Artist = bToString(b[nsfARTIST:]) n.Copyright = bToString(b[nsfCOPYRIGHT:]) n.SpeedNTSC = bLEtoUint16(b[nsfSPEED_NTSC:]) - println("SNTSC", n.SpeedNTSC) copy(n.Bankswitch[:], b[nsfBANKSWITCH:nsfSPEED_PAL]) n.Data = b[nsfHEADER_LEN:] return &n, nil @@ -139,9 +138,6 @@ func ReadNSFE(b []byte) (*NSF, error) { panic(id) } } - for i, s := range n.Songs { - fmt.Println(i, s.Name, s.Duration, s.Fade) - } return &n, nil } diff --git a/_third_party/github.com/oov/directsound-go/dsound/ds_test.go b/_third_party/github.com/oov/directsound-go/dsound/ds_test.go deleted file mode 100644 index b314b41..0000000 --- a/_third_party/github.com/oov/directsound-go/dsound/ds_test.go +++ /dev/null @@ -1,221 +0,0 @@ -package dsound - -import ( - "math/rand" - "syscall" - "testing" - "time" -) - -var ( - user32 = syscall.MustLoadDLL("user32.dll") - GetDesktopWindow = user32.MustFindProc("GetDesktopWindow") -) - -func TestDirectSoundEnumerate(t *testing.T) { - err := DirectSoundEnumerate(func(guid *GUID, description string, module string) bool { - t.Log(guid, description, module) - return true - }) - if err != nil { - t.Error(err) - } -} - -func TestDirectSoundCaptureEnumerate(t *testing.T) { - err := DirectSoundCaptureEnumerate(func(guid *GUID, description string, module string) bool { - t.Log(guid, description, module) - return true - }) - if err != nil { - t.Error(err) - } -} - -func initDirectSound() *IDirectSound { - hasDefaultDevice := false - DirectSoundEnumerate(func(guid *GUID, description string, module string) bool { - if guid == nil { - hasDefaultDevice = true - return false - } - return true - }) - if !hasDefaultDevice { - return nil - } - - ds, err := DirectSoundCreate(nil) - if err != nil { - panic(err) - } - - desktopWindow, _, err := GetDesktopWindow.Call() - err = ds.SetCooperativeLevel(syscall.Handle(desktopWindow), DSSCL_PRIORITY) - if err != nil { - panic(err) - } - - return ds -} - -func TestIDirectSound(t *testing.T) { - ds := initDirectSound() - if ds == nil { - t.Skip("No devices.") - } - - defer ds.Release() - - caps, err := ds.GetCaps() - if err != nil { - t.Error(err) - } - t.Log(caps) -} - -func TestIDirectSoundBufferStatic(t *testing.T) { - const SampleRate = 44100 - const Bits = 16 - const Channels = 2 - const BytesPerSec = SampleRate * (Channels * Bits / 8) - - ds := initDirectSound() - if ds == nil { - t.Skip("No devices.") - } - defer ds.Release() - - // primary buffer - - primaryBuf, err := ds.CreateSoundBuffer(&BufferDesc{ - Flags: DSBCAPS_PRIMARYBUFFER, - BufferBytes: 0, - Format: nil, - }) - if err != nil { - t.Error(err) - } - bcaps, err := primaryBuf.GetCaps() - if err != nil { - t.Error(err) - } - t.Log("PrimaryBufferCaps:", bcaps) - - wfmbuf, err := primaryBuf.GetFormatBytes() - if err != nil { - t.Error(err) - } - t.Log("WaveFormat Bytes:", wfmbuf) - - wfex, err := primaryBuf.GetFormatWaveFormatEx() - if err != nil { - t.Error(err) - } - t.Log("WaveFormat WaveFormatEx:", wfex) - - wfext, err := primaryBuf.GetFormatWaveFormatExtensible() - if err != nil { - t.Error(err) - } - t.Log("WaveFormat WaveFormatExtensible:", wfext) - - err = primaryBuf.SetFormatWaveFormatEx(&WaveFormatEx{ - FormatTag: WAVE_FORMAT_PCM, - Channels: Channels, - SamplesPerSec: SampleRate, - BitsPerSample: Bits, - BlockAlign: Channels * Bits / 8, - AvgBytesPerSec: BytesPerSec, - ExtSize: 0, - }) - if err != nil { - t.Error(err) - } - - wfex, err = primaryBuf.GetFormatWaveFormatEx() - if err != nil { - t.Error(err) - } - t.Log("WaveFormat WaveFormatEx:", wfex) - - primaryBuf.Release() - - // secondary buffer - - secondaryBuf, err := ds.CreateSoundBuffer(&BufferDesc{ - Flags: DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPAN | DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCDEFER, - BufferBytes: BytesPerSec, - Format: &WaveFormatEx{ - FormatTag: WAVE_FORMAT_PCM, - Channels: Channels, - SamplesPerSec: SampleRate, - BitsPerSample: Bits, - BlockAlign: Channels * Bits / 8, - AvgBytesPerSec: BytesPerSec, - ExtSize: 0, - }, - }) - if err != nil { - t.Error(err) - } - defer secondaryBuf.Release() - - err = secondaryBuf.SetVolume(0) - if err != nil { - t.Error(err) - } - - vol, err := secondaryBuf.GetVolume() - if err != nil { - t.Error(err) - } - t.Log("Volume:", vol) - - err = secondaryBuf.SetPan(0) - if err != nil { - t.Error(err) - } - - pan, err := secondaryBuf.GetPan() - if err != nil { - t.Error(err) - } - t.Log("Pan:", pan) - - freq, err := secondaryBuf.GetFrequency() - if err != nil { - t.Error(err) - } - t.Log("Frequncy:", freq) - - is1, is2, err := secondaryBuf.LockInt16s(0, BytesPerSec, 0) - if err != nil { - t.Error(err) - } - t.Log("LockInt16s Buf1Len:", len(is1), "Buf2Len:", len(is2)) - - // noise fade-in - p, ld4 := 0.0, float64(len(is1)) - for i := range is1 { - is1[i] = int16((rand.Float64()*10000 - 5000) * (p / ld4)) - p += 1 - } - err = secondaryBuf.UnlockInt16s(is1, is2) - if err != nil { - t.Error(err) - } - - err = secondaryBuf.Play(0, DSBPLAY_LOOPING) - if err != nil { - t.Error(err) - } - - status, err := secondaryBuf.GetStatus() - if err != nil { - t.Error(err) - } - t.Log("Status:", status) - - time.Sleep(time.Second) -} diff --git a/_third_party/github.com/pkg/browser/LICENSE b/_third_party/github.com/pkg/browser/LICENSE deleted file mode 100644 index 65f78fb..0000000 --- a/_third_party/github.com/pkg/browser/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2014, Dave Cheney -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/_third_party/github.com/pkg/browser/README.md b/_third_party/github.com/pkg/browser/README.md deleted file mode 100644 index 69c00ca..0000000 --- a/_third_party/github.com/pkg/browser/README.md +++ /dev/null @@ -1,44 +0,0 @@ - -# browser - import "github.com/pkg/browser" - -Package browser provides helpers to open files, readers, and urls in a browser window. - -The choice of which browser is started is entirely client dependant. - - - - - - -## func OpenFile -``` go -func OpenFile(path string) error -``` -OpenFile opens new browser window for the file path. - - -## func OpenReader -``` go -func OpenReader(r io.Reader) error -``` -OpenReader consumes the contents of r and presents the -results in a new browser window. - - -## func OpenURL -``` go -func OpenURL(url string) error -``` -OpenURL opens a new browser window pointing to url. - - - - - - - - - -- - - -Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) \ No newline at end of file diff --git a/_third_party/github.com/pkg/browser/example_test.go b/_third_party/github.com/pkg/browser/example_test.go deleted file mode 100644 index a6fbe16..0000000 --- a/_third_party/github.com/pkg/browser/example_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package browser - -import "strings" - -func ExampleOpenFile() { - OpenFile("index.html") -} - -func ExampleOpenReader() { - // https://github.com/rust-lang/rust/issues/13871 - const quote = `There was a night when winds from unknown spaces -whirled us irresistibly into limitless vacum beyond all thought and entity. -Perceptions of the most maddeningly untransmissible sort thronged upon us; -perceptions of infinity which at the time convulsed us with joy, yet which -are now partly lost to my memory and partly incapable of presentation to others.` - r := strings.NewReader(quote) - OpenReader(r) -} - -func ExampleOpenURL() { - const url = "http://golang.org/" - OpenURL(url) -} diff --git a/_third_party/golang.org/x/net/context/context.go b/_third_party/golang.org/x/net/context/context.go index c088f66..a50f80b 100644 --- a/_third_party/golang.org/x/net/context/context.go +++ b/_third_party/golang.org/x/net/context/context.go @@ -64,18 +64,21 @@ type Context interface { // // Done is provided for use in select statements: // - // // CancelableOperation calls UncancelableOperation and returns as soon as - // // it returns or ctx.Done is closed. - // func CancelableOperation(ctx context.Context) (Result, error) { - // c := make(chan Result, 1) - // go func() { c <- UncancelableOperation() }() - // select { - // case res := <-c: - // return res, nil - // case <-ctx.Done(): - // return nil, ctx.Err() - // } - // } + // // Stream generates values with DoSomething and sends them to out + // // until DoSomething returns an error or ctx.Done is closed. + // func Stream(ctx context.Context, out <-chan Value) error { + // for { + // v, err := DoSomething(ctx) + // if err != nil { + // return err + // } + // select { + // case <-ctx.Done(): + // return ctx.Err() + // case out <- v: + // } + // } + // } // // See http://blog.golang.org/pipelines for more examples of how to use // a Done channel for cancelation. @@ -202,6 +205,9 @@ type CancelFunc func() // WithCancel returns a copy of parent with a new Done channel. The returned // context's Done channel is closed when the returned cancel function is called // or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { c := newCancelCtx(parent) propagateCancel(parent, &c) @@ -262,6 +268,19 @@ func parentCancelCtx(parent Context) (*cancelCtx, bool) { } } +// removeChild removes a context from its parent. +func removeChild(parent Context, child canceler) { + p, ok := parentCancelCtx(parent) + if !ok { + return + } + p.mu.Lock() + if p.children != nil { + delete(p.children, child) + } + p.mu.Unlock() +} + // A canceler is a context type that can be canceled directly. The // implementations are *cancelCtx and *timerCtx. type canceler interface { @@ -316,13 +335,7 @@ func (c *cancelCtx) cancel(removeFromParent bool, err error) { c.mu.Unlock() if removeFromParent { - if p, ok := parentCancelCtx(c.Context); ok { - p.mu.Lock() - if p.children != nil { - delete(p.children, c) - } - p.mu.Unlock() - } + removeChild(c.Context, c) } } @@ -333,9 +346,8 @@ func (c *cancelCtx) cancel(removeFromParent bool, err error) { // cancel function is called, or when the parent context's Done channel is // closed, whichever happens first. // -// Canceling this context releases resources associated with the deadline -// timer, so code should call cancel as soon as the operations running in this -// Context complete. +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { // The current deadline is already sooner than the new one. @@ -380,7 +392,11 @@ func (c *timerCtx) String() string { } func (c *timerCtx) cancel(removeFromParent bool, err error) { - c.cancelCtx.cancel(removeFromParent, err) + c.cancelCtx.cancel(false, err) + if removeFromParent { + // Remove this timerCtx from its parent cancelCtx's children. + removeChild(c.cancelCtx.Context, c) + } c.mu.Lock() if c.timer != nil { c.timer.Stop() @@ -391,9 +407,8 @@ func (c *timerCtx) cancel(removeFromParent bool, err error) { // WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). // -// Canceling this context releases resources associated with the deadline -// timer, so code should call cancel as soon as the operations running in this -// Context complete: +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: // // func slowOperationWithTimeout(ctx context.Context) (Result, error) { // ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) diff --git a/_third_party/golang.org/x/net/context/context_test.go b/_third_party/golang.org/x/net/context/context_test.go deleted file mode 100644 index 82d2494..0000000 --- a/_third_party/golang.org/x/net/context/context_test.go +++ /dev/null @@ -1,553 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package context - -import ( - "fmt" - "math/rand" - "runtime" - "strings" - "sync" - "testing" - "time" -) - -// otherContext is a Context that's not one of the types defined in context.go. -// This lets us test code paths that differ based on the underlying type of the -// Context. -type otherContext struct { - Context -} - -func TestBackground(t *testing.T) { - c := Background() - if c == nil { - t.Fatalf("Background returned nil") - } - select { - case x := <-c.Done(): - t.Errorf("<-c.Done() == %v want nothing (it should block)", x) - default: - } - if got, want := fmt.Sprint(c), "context.Background"; got != want { - t.Errorf("Background().String() = %q want %q", got, want) - } -} - -func TestTODO(t *testing.T) { - c := TODO() - if c == nil { - t.Fatalf("TODO returned nil") - } - select { - case x := <-c.Done(): - t.Errorf("<-c.Done() == %v want nothing (it should block)", x) - default: - } - if got, want := fmt.Sprint(c), "context.TODO"; got != want { - t.Errorf("TODO().String() = %q want %q", got, want) - } -} - -func TestWithCancel(t *testing.T) { - c1, cancel := WithCancel(Background()) - - if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want { - t.Errorf("c1.String() = %q want %q", got, want) - } - - o := otherContext{c1} - c2, _ := WithCancel(o) - contexts := []Context{c1, o, c2} - - for i, c := range contexts { - if d := c.Done(); d == nil { - t.Errorf("c[%d].Done() == %v want non-nil", i, d) - } - if e := c.Err(); e != nil { - t.Errorf("c[%d].Err() == %v want nil", i, e) - } - - select { - case x := <-c.Done(): - t.Errorf("<-c.Done() == %v want nothing (it should block)", x) - default: - } - } - - cancel() - time.Sleep(100 * time.Millisecond) // let cancelation propagate - - for i, c := range contexts { - select { - case <-c.Done(): - default: - t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i) - } - if e := c.Err(); e != Canceled { - t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled) - } - } -} - -func TestParentFinishesChild(t *testing.T) { - // Context tree: - // parent -> cancelChild - // parent -> valueChild -> timerChild - parent, cancel := WithCancel(Background()) - cancelChild, stop := WithCancel(parent) - defer stop() - valueChild := WithValue(parent, "key", "value") - timerChild, stop := WithTimeout(valueChild, 10000*time.Hour) - defer stop() - - select { - case x := <-parent.Done(): - t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) - case x := <-cancelChild.Done(): - t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x) - case x := <-timerChild.Done(): - t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x) - case x := <-valueChild.Done(): - t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x) - default: - } - - // The parent's children should contain the two cancelable children. - pc := parent.(*cancelCtx) - cc := cancelChild.(*cancelCtx) - tc := timerChild.(*timerCtx) - pc.mu.Lock() - if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] { - t.Errorf("bad linkage: pc.children = %v, want %v and %v", - pc.children, cc, tc) - } - pc.mu.Unlock() - - if p, ok := parentCancelCtx(cc.Context); !ok || p != pc { - t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc) - } - if p, ok := parentCancelCtx(tc.Context); !ok || p != pc { - t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc) - } - - cancel() - - pc.mu.Lock() - if len(pc.children) != 0 { - t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children) - } - pc.mu.Unlock() - - // parent and children should all be finished. - check := func(ctx Context, name string) { - select { - case <-ctx.Done(): - default: - t.Errorf("<-%s.Done() blocked, but shouldn't have", name) - } - if e := ctx.Err(); e != Canceled { - t.Errorf("%s.Err() == %v want %v", name, e, Canceled) - } - } - check(parent, "parent") - check(cancelChild, "cancelChild") - check(valueChild, "valueChild") - check(timerChild, "timerChild") - - // WithCancel should return a canceled context on a canceled parent. - precanceledChild := WithValue(parent, "key", "value") - select { - case <-precanceledChild.Done(): - default: - t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have") - } - if e := precanceledChild.Err(); e != Canceled { - t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled) - } -} - -func TestChildFinishesFirst(t *testing.T) { - cancelable, stop := WithCancel(Background()) - defer stop() - for _, parent := range []Context{Background(), cancelable} { - child, cancel := WithCancel(parent) - - select { - case x := <-parent.Done(): - t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) - case x := <-child.Done(): - t.Errorf("<-child.Done() == %v want nothing (it should block)", x) - default: - } - - cc := child.(*cancelCtx) - pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background() - if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) { - t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok) - } - - if pcok { - pc.mu.Lock() - if len(pc.children) != 1 || !pc.children[cc] { - t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc) - } - pc.mu.Unlock() - } - - cancel() - - if pcok { - pc.mu.Lock() - if len(pc.children) != 0 { - t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children) - } - pc.mu.Unlock() - } - - // child should be finished. - select { - case <-child.Done(): - default: - t.Errorf("<-child.Done() blocked, but shouldn't have") - } - if e := child.Err(); e != Canceled { - t.Errorf("child.Err() == %v want %v", e, Canceled) - } - - // parent should not be finished. - select { - case x := <-parent.Done(): - t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) - default: - } - if e := parent.Err(); e != nil { - t.Errorf("parent.Err() == %v want nil", e) - } - } -} - -func testDeadline(c Context, wait time.Duration, t *testing.T) { - select { - case <-time.After(wait): - t.Fatalf("context should have timed out") - case <-c.Done(): - } - if e := c.Err(); e != DeadlineExceeded { - t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded) - } -} - -func TestDeadline(t *testing.T) { - c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) - if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { - t.Errorf("c.String() = %q want prefix %q", got, prefix) - } - testDeadline(c, 200*time.Millisecond, t) - - c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) - o := otherContext{c} - testDeadline(o, 200*time.Millisecond, t) - - c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) - o = otherContext{c} - c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond)) - testDeadline(c, 200*time.Millisecond, t) -} - -func TestTimeout(t *testing.T) { - c, _ := WithTimeout(Background(), 100*time.Millisecond) - if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { - t.Errorf("c.String() = %q want prefix %q", got, prefix) - } - testDeadline(c, 200*time.Millisecond, t) - - c, _ = WithTimeout(Background(), 100*time.Millisecond) - o := otherContext{c} - testDeadline(o, 200*time.Millisecond, t) - - c, _ = WithTimeout(Background(), 100*time.Millisecond) - o = otherContext{c} - c, _ = WithTimeout(o, 300*time.Millisecond) - testDeadline(c, 200*time.Millisecond, t) -} - -func TestCanceledTimeout(t *testing.T) { - c, _ := WithTimeout(Background(), 200*time.Millisecond) - o := otherContext{c} - c, cancel := WithTimeout(o, 400*time.Millisecond) - cancel() - time.Sleep(100 * time.Millisecond) // let cancelation propagate - select { - case <-c.Done(): - default: - t.Errorf("<-c.Done() blocked, but shouldn't have") - } - if e := c.Err(); e != Canceled { - t.Errorf("c.Err() == %v want %v", e, Canceled) - } -} - -type key1 int -type key2 int - -var k1 = key1(1) -var k2 = key2(1) // same int as k1, different type -var k3 = key2(3) // same type as k2, different int - -func TestValues(t *testing.T) { - check := func(c Context, nm, v1, v2, v3 string) { - if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 { - t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0) - } - if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 { - t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0) - } - if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 { - t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0) - } - } - - c0 := Background() - check(c0, "c0", "", "", "") - - c1 := WithValue(Background(), k1, "c1k1") - check(c1, "c1", "c1k1", "", "") - - if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want { - t.Errorf("c.String() = %q want %q", got, want) - } - - c2 := WithValue(c1, k2, "c2k2") - check(c2, "c2", "c1k1", "c2k2", "") - - c3 := WithValue(c2, k3, "c3k3") - check(c3, "c2", "c1k1", "c2k2", "c3k3") - - c4 := WithValue(c3, k1, nil) - check(c4, "c4", "", "c2k2", "c3k3") - - o0 := otherContext{Background()} - check(o0, "o0", "", "", "") - - o1 := otherContext{WithValue(Background(), k1, "c1k1")} - check(o1, "o1", "c1k1", "", "") - - o2 := WithValue(o1, k2, "o2k2") - check(o2, "o2", "c1k1", "o2k2", "") - - o3 := otherContext{c4} - check(o3, "o3", "", "c2k2", "c3k3") - - o4 := WithValue(o3, k3, nil) - check(o4, "o4", "", "c2k2", "") -} - -func TestAllocs(t *testing.T) { - bg := Background() - for _, test := range []struct { - desc string - f func() - limit float64 - gccgoLimit float64 - }{ - { - desc: "Background()", - f: func() { Background() }, - limit: 0, - gccgoLimit: 0, - }, - { - desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1), - f: func() { - c := WithValue(bg, k1, nil) - c.Value(k1) - }, - limit: 3, - gccgoLimit: 3, - }, - { - desc: "WithTimeout(bg, 15*time.Millisecond)", - f: func() { - c, _ := WithTimeout(bg, 15*time.Millisecond) - <-c.Done() - }, - limit: 8, - gccgoLimit: 13, - }, - { - desc: "WithCancel(bg)", - f: func() { - c, cancel := WithCancel(bg) - cancel() - <-c.Done() - }, - limit: 5, - gccgoLimit: 8, - }, - { - desc: "WithTimeout(bg, 100*time.Millisecond)", - f: func() { - c, cancel := WithTimeout(bg, 100*time.Millisecond) - cancel() - <-c.Done() - }, - limit: 8, - gccgoLimit: 25, - }, - } { - limit := test.limit - if runtime.Compiler == "gccgo" { - // gccgo does not yet do escape analysis. - // TOOD(iant): Remove this when gccgo does do escape analysis. - limit = test.gccgoLimit - } - if n := testing.AllocsPerRun(100, test.f); n > limit { - t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit)) - } - } -} - -func TestSimultaneousCancels(t *testing.T) { - root, cancel := WithCancel(Background()) - m := map[Context]CancelFunc{root: cancel} - q := []Context{root} - // Create a tree of contexts. - for len(q) != 0 && len(m) < 100 { - parent := q[0] - q = q[1:] - for i := 0; i < 4; i++ { - ctx, cancel := WithCancel(parent) - m[ctx] = cancel - q = append(q, ctx) - } - } - // Start all the cancels in a random order. - var wg sync.WaitGroup - wg.Add(len(m)) - for _, cancel := range m { - go func(cancel CancelFunc) { - cancel() - wg.Done() - }(cancel) - } - // Wait on all the contexts in a random order. - for ctx := range m { - select { - case <-ctx.Done(): - case <-time.After(1 * time.Second): - buf := make([]byte, 10<<10) - n := runtime.Stack(buf, true) - t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n]) - } - } - // Wait for all the cancel functions to return. - done := make(chan struct{}) - go func() { - wg.Wait() - close(done) - }() - select { - case <-done: - case <-time.After(1 * time.Second): - buf := make([]byte, 10<<10) - n := runtime.Stack(buf, true) - t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n]) - } -} - -func TestInterlockedCancels(t *testing.T) { - parent, cancelParent := WithCancel(Background()) - child, cancelChild := WithCancel(parent) - go func() { - parent.Done() - cancelChild() - }() - cancelParent() - select { - case <-child.Done(): - case <-time.After(1 * time.Second): - buf := make([]byte, 10<<10) - n := runtime.Stack(buf, true) - t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n]) - } -} - -func TestLayersCancel(t *testing.T) { - testLayers(t, time.Now().UnixNano(), false) -} - -func TestLayersTimeout(t *testing.T) { - testLayers(t, time.Now().UnixNano(), true) -} - -func testLayers(t *testing.T, seed int64, testTimeout bool) { - rand.Seed(seed) - errorf := func(format string, a ...interface{}) { - t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...) - } - const ( - timeout = 200 * time.Millisecond - minLayers = 30 - ) - type value int - var ( - vals []*value - cancels []CancelFunc - numTimers int - ctx = Background() - ) - for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ { - switch rand.Intn(3) { - case 0: - v := new(value) - ctx = WithValue(ctx, v, v) - vals = append(vals, v) - case 1: - var cancel CancelFunc - ctx, cancel = WithCancel(ctx) - cancels = append(cancels, cancel) - case 2: - var cancel CancelFunc - ctx, cancel = WithTimeout(ctx, timeout) - cancels = append(cancels, cancel) - numTimers++ - } - } - checkValues := func(when string) { - for _, key := range vals { - if val := ctx.Value(key).(*value); key != val { - errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key) - } - } - } - select { - case <-ctx.Done(): - errorf("ctx should not be canceled yet") - default: - } - if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) { - t.Errorf("ctx.String() = %q want prefix %q", s, prefix) - } - t.Log(ctx) - checkValues("before cancel") - if testTimeout { - select { - case <-ctx.Done(): - case <-time.After(timeout + timeout/10): - errorf("ctx should have timed out") - } - checkValues("after timeout") - } else { - cancel := cancels[rand.Intn(len(cancels))] - cancel() - select { - case <-ctx.Done(): - default: - errorf("ctx should be canceled") - } - checkValues("after cancel") - } -} diff --git a/_third_party/golang.org/x/net/context/withtimeout_test.go b/_third_party/golang.org/x/net/context/withtimeout_test.go deleted file mode 100644 index 9dd4164..0000000 --- a/_third_party/golang.org/x/net/context/withtimeout_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package context_test - -import ( - "fmt" - "time" - - "github.com/mjibson/mog/_third_party/golang.org/x/net/context" -) - -func ExampleWithTimeout() { - // Pass a context with a timeout to tell a blocking function that it - // should abandon its work after the timeout elapses. - ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond) - select { - case <-time.After(200 * time.Millisecond): - fmt.Println("overslept") - case <-ctx.Done(): - fmt.Println(ctx.Err()) // prints "context deadline exceeded" - } - // Output: - // context deadline exceeded -} diff --git a/_third_party/golang.org/x/net/websocket/client.go b/_third_party/golang.org/x/net/websocket/client.go index ef11a51..20d1e1e 100644 --- a/_third_party/golang.org/x/net/websocket/client.go +++ b/_third_party/golang.org/x/net/websocket/client.go @@ -103,6 +103,7 @@ func DialConfig(config *Config) (ws *Conn, err error) { ws, err = NewClient(config, client) if err != nil { + client.Close() goto Error } return diff --git a/_third_party/golang.org/x/net/websocket/exampledial_test.go b/_third_party/golang.org/x/net/websocket/exampledial_test.go deleted file mode 100644 index cd56bae..0000000 --- a/_third_party/golang.org/x/net/websocket/exampledial_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket_test - -import ( - "fmt" - "log" - - "github.com/mjibson/mog/_third_party/golang.org/x/net/websocket" -) - -// This example demonstrates a trivial client. -func ExampleDial() { - origin := "http://localhost/" - url := "ws://localhost:12345/ws" - ws, err := websocket.Dial(url, "", origin) - if err != nil { - log.Fatal(err) - } - if _, err := ws.Write([]byte("hello, world!\n")); err != nil { - log.Fatal(err) - } - var msg = make([]byte, 512) - var n int - if n, err = ws.Read(msg); err != nil { - log.Fatal(err) - } - fmt.Printf("Received: %s.\n", msg[:n]) -} diff --git a/_third_party/golang.org/x/net/websocket/examplehandler_test.go b/_third_party/golang.org/x/net/websocket/examplehandler_test.go deleted file mode 100644 index 9e2fb28..0000000 --- a/_third_party/golang.org/x/net/websocket/examplehandler_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket_test - -import ( - "io" - "net/http" - - "github.com/mjibson/mog/_third_party/golang.org/x/net/websocket" -) - -// Echo the data received on the WebSocket. -func EchoServer(ws *websocket.Conn) { - io.Copy(ws, ws) -} - -// This example demonstrates a trivial echo server. -func ExampleHandler() { - http.Handle("/echo", websocket.Handler(EchoServer)) - err := http.ListenAndServe(":12345", nil) - if err != nil { - panic("ListenAndServe: " + err.Error()) - } -} diff --git a/_third_party/golang.org/x/net/websocket/hybi_test.go b/_third_party/golang.org/x/net/websocket/hybi_test.go deleted file mode 100644 index d6a1910..0000000 --- a/_third_party/golang.org/x/net/websocket/hybi_test.go +++ /dev/null @@ -1,590 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bufio" - "bytes" - "fmt" - "io" - "net/http" - "net/url" - "strings" - "testing" -) - -// Test the getNonceAccept function with values in -// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 -func TestSecWebSocketAccept(t *testing.T) { - nonce := []byte("dGhlIHNhbXBsZSBub25jZQ==") - expected := []byte("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") - accept, err := getNonceAccept(nonce) - if err != nil { - t.Errorf("getNonceAccept: returned error %v", err) - return - } - if !bytes.Equal(expected, accept) { - t.Errorf("getNonceAccept: expected %q got %q", expected, accept) - } -} - -func TestHybiClientHandshake(t *testing.T) { - b := bytes.NewBuffer([]byte{}) - bw := bufio.NewWriter(b) - br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols -Upgrade: websocket -Connection: Upgrade -Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= -Sec-WebSocket-Protocol: chat - -`)) - var err error - config := new(Config) - config.Location, err = url.ParseRequestURI("ws://server.example.com/chat") - if err != nil { - t.Fatal("location url", err) - } - config.Origin, err = url.ParseRequestURI("http://example.com") - if err != nil { - t.Fatal("origin url", err) - } - config.Protocol = append(config.Protocol, "chat") - config.Protocol = append(config.Protocol, "superchat") - config.Version = ProtocolVersionHybi13 - - config.handshakeData = map[string]string{ - "key": "dGhlIHNhbXBsZSBub25jZQ==", - } - err = hybiClientHandshake(config, br, bw) - if err != nil { - t.Errorf("handshake failed: %v", err) - } - req, err := http.ReadRequest(bufio.NewReader(b)) - if err != nil { - t.Fatalf("read request: %v", err) - } - if req.Method != "GET" { - t.Errorf("request method expected GET, but got %q", req.Method) - } - if req.URL.Path != "/chat" { - t.Errorf("request path expected /chat, but got %q", req.URL.Path) - } - if req.Proto != "HTTP/1.1" { - t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto) - } - if req.Host != "server.example.com" { - t.Errorf("request Host expected server.example.com, but got %v", req.Host) - } - var expectedHeader = map[string]string{ - "Connection": "Upgrade", - "Upgrade": "websocket", - "Sec-Websocket-Key": config.handshakeData["key"], - "Origin": config.Origin.String(), - "Sec-Websocket-Protocol": "chat, superchat", - "Sec-Websocket-Version": fmt.Sprintf("%d", ProtocolVersionHybi13), - } - for k, v := range expectedHeader { - if req.Header.Get(k) != v { - t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k))) - } - } -} - -func TestHybiClientHandshakeWithHeader(t *testing.T) { - b := bytes.NewBuffer([]byte{}) - bw := bufio.NewWriter(b) - br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols -Upgrade: websocket -Connection: Upgrade -Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= -Sec-WebSocket-Protocol: chat - -`)) - var err error - config := new(Config) - config.Location, err = url.ParseRequestURI("ws://server.example.com/chat") - if err != nil { - t.Fatal("location url", err) - } - config.Origin, err = url.ParseRequestURI("http://example.com") - if err != nil { - t.Fatal("origin url", err) - } - config.Protocol = append(config.Protocol, "chat") - config.Protocol = append(config.Protocol, "superchat") - config.Version = ProtocolVersionHybi13 - config.Header = http.Header(make(map[string][]string)) - config.Header.Add("User-Agent", "test") - - config.handshakeData = map[string]string{ - "key": "dGhlIHNhbXBsZSBub25jZQ==", - } - err = hybiClientHandshake(config, br, bw) - if err != nil { - t.Errorf("handshake failed: %v", err) - } - req, err := http.ReadRequest(bufio.NewReader(b)) - if err != nil { - t.Fatalf("read request: %v", err) - } - if req.Method != "GET" { - t.Errorf("request method expected GET, but got %q", req.Method) - } - if req.URL.Path != "/chat" { - t.Errorf("request path expected /chat, but got %q", req.URL.Path) - } - if req.Proto != "HTTP/1.1" { - t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto) - } - if req.Host != "server.example.com" { - t.Errorf("request Host expected server.example.com, but got %v", req.Host) - } - var expectedHeader = map[string]string{ - "Connection": "Upgrade", - "Upgrade": "websocket", - "Sec-Websocket-Key": config.handshakeData["key"], - "Origin": config.Origin.String(), - "Sec-Websocket-Protocol": "chat, superchat", - "Sec-Websocket-Version": fmt.Sprintf("%d", ProtocolVersionHybi13), - "User-Agent": "test", - } - for k, v := range expectedHeader { - if req.Header.Get(k) != v { - t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k))) - } - } -} - -func TestHybiServerHandshake(t *testing.T) { - config := new(Config) - handshaker := &hybiServerHandshaker{Config: config} - br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 -Host: server.example.com -Upgrade: websocket -Connection: Upgrade -Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== -Origin: http://example.com -Sec-WebSocket-Protocol: chat, superchat -Sec-WebSocket-Version: 13 - -`)) - req, err := http.ReadRequest(br) - if err != nil { - t.Fatal("request", err) - } - code, err := handshaker.ReadHandshake(br, req) - if err != nil { - t.Errorf("handshake failed: %v", err) - } - if code != http.StatusSwitchingProtocols { - t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code) - } - expectedProtocols := []string{"chat", "superchat"} - if fmt.Sprintf("%v", config.Protocol) != fmt.Sprintf("%v", expectedProtocols) { - t.Errorf("protocol expected %q but got %q", expectedProtocols, config.Protocol) - } - b := bytes.NewBuffer([]byte{}) - bw := bufio.NewWriter(b) - - config.Protocol = config.Protocol[:1] - - err = handshaker.AcceptHandshake(bw) - if err != nil { - t.Errorf("handshake response failed: %v", err) - } - expectedResponse := strings.Join([]string{ - "HTTP/1.1 101 Switching Protocols", - "Upgrade: websocket", - "Connection: Upgrade", - "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", - "Sec-WebSocket-Protocol: chat", - "", ""}, "\r\n") - - if b.String() != expectedResponse { - t.Errorf("handshake expected %q but got %q", expectedResponse, b.String()) - } -} - -func TestHybiServerHandshakeNoSubProtocol(t *testing.T) { - config := new(Config) - handshaker := &hybiServerHandshaker{Config: config} - br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 -Host: server.example.com -Upgrade: websocket -Connection: Upgrade -Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== -Origin: http://example.com -Sec-WebSocket-Version: 13 - -`)) - req, err := http.ReadRequest(br) - if err != nil { - t.Fatal("request", err) - } - code, err := handshaker.ReadHandshake(br, req) - if err != nil { - t.Errorf("handshake failed: %v", err) - } - if code != http.StatusSwitchingProtocols { - t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code) - } - if len(config.Protocol) != 0 { - t.Errorf("len(config.Protocol) expected 0, but got %q", len(config.Protocol)) - } - b := bytes.NewBuffer([]byte{}) - bw := bufio.NewWriter(b) - - err = handshaker.AcceptHandshake(bw) - if err != nil { - t.Errorf("handshake response failed: %v", err) - } - expectedResponse := strings.Join([]string{ - "HTTP/1.1 101 Switching Protocols", - "Upgrade: websocket", - "Connection: Upgrade", - "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", - "", ""}, "\r\n") - - if b.String() != expectedResponse { - t.Errorf("handshake expected %q but got %q", expectedResponse, b.String()) - } -} - -func TestHybiServerHandshakeHybiBadVersion(t *testing.T) { - config := new(Config) - handshaker := &hybiServerHandshaker{Config: config} - br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 -Host: server.example.com -Upgrade: websocket -Connection: Upgrade -Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== -Sec-WebSocket-Origin: http://example.com -Sec-WebSocket-Protocol: chat, superchat -Sec-WebSocket-Version: 9 - -`)) - req, err := http.ReadRequest(br) - if err != nil { - t.Fatal("request", err) - } - code, err := handshaker.ReadHandshake(br, req) - if err != ErrBadWebSocketVersion { - t.Errorf("handshake expected err %q but got %q", ErrBadWebSocketVersion, err) - } - if code != http.StatusBadRequest { - t.Errorf("status expected %q but got %q", http.StatusBadRequest, code) - } -} - -func testHybiFrame(t *testing.T, testHeader, testPayload, testMaskedPayload []byte, frameHeader *hybiFrameHeader) { - b := bytes.NewBuffer([]byte{}) - frameWriterFactory := &hybiFrameWriterFactory{bufio.NewWriter(b), false} - w, _ := frameWriterFactory.NewFrameWriter(TextFrame) - w.(*hybiFrameWriter).header = frameHeader - _, err := w.Write(testPayload) - w.Close() - if err != nil { - t.Errorf("Write error %q", err) - } - var expectedFrame []byte - expectedFrame = append(expectedFrame, testHeader...) - expectedFrame = append(expectedFrame, testMaskedPayload...) - if !bytes.Equal(expectedFrame, b.Bytes()) { - t.Errorf("frame expected %q got %q", expectedFrame, b.Bytes()) - } - frameReaderFactory := &hybiFrameReaderFactory{bufio.NewReader(b)} - r, err := frameReaderFactory.NewFrameReader() - if err != nil { - t.Errorf("Read error %q", err) - } - if header := r.HeaderReader(); header == nil { - t.Errorf("no header") - } else { - actualHeader := make([]byte, r.Len()) - n, err := header.Read(actualHeader) - if err != nil { - t.Errorf("Read header error %q", err) - } else { - if n < len(testHeader) { - t.Errorf("header too short %q got %q", testHeader, actualHeader[:n]) - } - if !bytes.Equal(testHeader, actualHeader[:n]) { - t.Errorf("header expected %q got %q", testHeader, actualHeader[:n]) - } - } - } - if trailer := r.TrailerReader(); trailer != nil { - t.Errorf("unexpected trailer %q", trailer) - } - frame := r.(*hybiFrameReader) - if frameHeader.Fin != frame.header.Fin || - frameHeader.OpCode != frame.header.OpCode || - len(testPayload) != int(frame.header.Length) { - t.Errorf("mismatch %v (%d) vs %v", frameHeader, len(testPayload), frame) - } - payload := make([]byte, len(testPayload)) - _, err = r.Read(payload) - if err != nil { - t.Errorf("read %v", err) - } - if !bytes.Equal(testPayload, payload) { - t.Errorf("payload %q vs %q", testPayload, payload) - } -} - -func TestHybiShortTextFrame(t *testing.T) { - frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame} - payload := []byte("hello") - testHybiFrame(t, []byte{0x81, 0x05}, payload, payload, frameHeader) - - payload = make([]byte, 125) - testHybiFrame(t, []byte{0x81, 125}, payload, payload, frameHeader) -} - -func TestHybiShortMaskedTextFrame(t *testing.T) { - frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame, - MaskingKey: []byte{0xcc, 0x55, 0x80, 0x20}} - payload := []byte("hello") - maskedPayload := []byte{0xa4, 0x30, 0xec, 0x4c, 0xa3} - header := []byte{0x81, 0x85} - header = append(header, frameHeader.MaskingKey...) - testHybiFrame(t, header, payload, maskedPayload, frameHeader) -} - -func TestHybiShortBinaryFrame(t *testing.T) { - frameHeader := &hybiFrameHeader{Fin: true, OpCode: BinaryFrame} - payload := []byte("hello") - testHybiFrame(t, []byte{0x82, 0x05}, payload, payload, frameHeader) - - payload = make([]byte, 125) - testHybiFrame(t, []byte{0x82, 125}, payload, payload, frameHeader) -} - -func TestHybiControlFrame(t *testing.T) { - frameHeader := &hybiFrameHeader{Fin: true, OpCode: PingFrame} - payload := []byte("hello") - testHybiFrame(t, []byte{0x89, 0x05}, payload, payload, frameHeader) - - frameHeader = &hybiFrameHeader{Fin: true, OpCode: PongFrame} - testHybiFrame(t, []byte{0x8A, 0x05}, payload, payload, frameHeader) - - frameHeader = &hybiFrameHeader{Fin: true, OpCode: CloseFrame} - payload = []byte{0x03, 0xe8} // 1000 - testHybiFrame(t, []byte{0x88, 0x02}, payload, payload, frameHeader) -} - -func TestHybiLongFrame(t *testing.T) { - frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame} - payload := make([]byte, 126) - testHybiFrame(t, []byte{0x81, 126, 0x00, 126}, payload, payload, frameHeader) - - payload = make([]byte, 65535) - testHybiFrame(t, []byte{0x81, 126, 0xff, 0xff}, payload, payload, frameHeader) - - payload = make([]byte, 65536) - testHybiFrame(t, []byte{0x81, 127, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}, payload, payload, frameHeader) -} - -func TestHybiClientRead(t *testing.T) { - wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o', - 0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping - 0x81, 0x05, 'w', 'o', 'r', 'l', 'd'} - br := bufio.NewReader(bytes.NewBuffer(wireData)) - bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) - conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil) - - msg := make([]byte, 512) - n, err := conn.Read(msg) - if err != nil { - t.Errorf("read 1st frame, error %q", err) - } - if n != 5 { - t.Errorf("read 1st frame, expect 5, got %d", n) - } - if !bytes.Equal(wireData[2:7], msg[:n]) { - t.Errorf("read 1st frame %v, got %v", wireData[2:7], msg[:n]) - } - n, err = conn.Read(msg) - if err != nil { - t.Errorf("read 2nd frame, error %q", err) - } - if n != 5 { - t.Errorf("read 2nd frame, expect 5, got %d", n) - } - if !bytes.Equal(wireData[16:21], msg[:n]) { - t.Errorf("read 2nd frame %v, got %v", wireData[16:21], msg[:n]) - } - n, err = conn.Read(msg) - if err == nil { - t.Errorf("read not EOF") - } - if n != 0 { - t.Errorf("expect read 0, got %d", n) - } -} - -func TestHybiShortRead(t *testing.T) { - wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o', - 0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping - 0x81, 0x05, 'w', 'o', 'r', 'l', 'd'} - br := bufio.NewReader(bytes.NewBuffer(wireData)) - bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) - conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil) - - step := 0 - pos := 0 - expectedPos := []int{2, 5, 16, 19} - expectedLen := []int{3, 2, 3, 2} - for { - msg := make([]byte, 3) - n, err := conn.Read(msg) - if step >= len(expectedPos) { - if err == nil { - t.Errorf("read not EOF") - } - if n != 0 { - t.Errorf("expect read 0, got %d", n) - } - return - } - pos = expectedPos[step] - endPos := pos + expectedLen[step] - if err != nil { - t.Errorf("read from %d, got error %q", pos, err) - return - } - if n != endPos-pos { - t.Errorf("read from %d, expect %d, got %d", pos, endPos-pos, n) - } - if !bytes.Equal(wireData[pos:endPos], msg[:n]) { - t.Errorf("read from %d, frame %v, got %v", pos, wireData[pos:endPos], msg[:n]) - } - step++ - } -} - -func TestHybiServerRead(t *testing.T) { - wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20, - 0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello - 0x89, 0x85, 0xcc, 0x55, 0x80, 0x20, - 0xa4, 0x30, 0xec, 0x4c, 0xa3, // ping: hello - 0x81, 0x85, 0xed, 0x83, 0xb4, 0x24, - 0x9a, 0xec, 0xc6, 0x48, 0x89, // world - } - br := bufio.NewReader(bytes.NewBuffer(wireData)) - bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) - conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request)) - - expected := [][]byte{[]byte("hello"), []byte("world")} - - msg := make([]byte, 512) - n, err := conn.Read(msg) - if err != nil { - t.Errorf("read 1st frame, error %q", err) - } - if n != 5 { - t.Errorf("read 1st frame, expect 5, got %d", n) - } - if !bytes.Equal(expected[0], msg[:n]) { - t.Errorf("read 1st frame %q, got %q", expected[0], msg[:n]) - } - - n, err = conn.Read(msg) - if err != nil { - t.Errorf("read 2nd frame, error %q", err) - } - if n != 5 { - t.Errorf("read 2nd frame, expect 5, got %d", n) - } - if !bytes.Equal(expected[1], msg[:n]) { - t.Errorf("read 2nd frame %q, got %q", expected[1], msg[:n]) - } - - n, err = conn.Read(msg) - if err == nil { - t.Errorf("read not EOF") - } - if n != 0 { - t.Errorf("expect read 0, got %d", n) - } -} - -func TestHybiServerReadWithoutMasking(t *testing.T) { - wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o'} - br := bufio.NewReader(bytes.NewBuffer(wireData)) - bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) - conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request)) - // server MUST close the connection upon receiving a non-masked frame. - msg := make([]byte, 512) - _, err := conn.Read(msg) - if err != io.EOF { - t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err) - } -} - -func TestHybiClientReadWithMasking(t *testing.T) { - wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20, - 0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello - } - br := bufio.NewReader(bytes.NewBuffer(wireData)) - bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) - conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil) - - // client MUST close the connection upon receiving a masked frame. - msg := make([]byte, 512) - _, err := conn.Read(msg) - if err != io.EOF { - t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err) - } -} - -// Test the hybiServerHandshaker supports firefox implementation and -// checks Connection request header include (but it's not necessary -// equal to) "upgrade" -func TestHybiServerFirefoxHandshake(t *testing.T) { - config := new(Config) - handshaker := &hybiServerHandshaker{Config: config} - br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 -Host: server.example.com -Upgrade: websocket -Connection: keep-alive, upgrade -Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== -Origin: http://example.com -Sec-WebSocket-Protocol: chat, superchat -Sec-WebSocket-Version: 13 - -`)) - req, err := http.ReadRequest(br) - if err != nil { - t.Fatal("request", err) - } - code, err := handshaker.ReadHandshake(br, req) - if err != nil { - t.Errorf("handshake failed: %v", err) - } - if code != http.StatusSwitchingProtocols { - t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code) - } - b := bytes.NewBuffer([]byte{}) - bw := bufio.NewWriter(b) - - config.Protocol = []string{"chat"} - - err = handshaker.AcceptHandshake(bw) - if err != nil { - t.Errorf("handshake response failed: %v", err) - } - expectedResponse := strings.Join([]string{ - "HTTP/1.1 101 Switching Protocols", - "Upgrade: websocket", - "Connection: Upgrade", - "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", - "Sec-WebSocket-Protocol: chat", - "", ""}, "\r\n") - - if b.String() != expectedResponse { - t.Errorf("handshake expected %q but got %q", expectedResponse, b.String()) - } -} diff --git a/_third_party/golang.org/x/net/websocket/websocket_test.go b/_third_party/golang.org/x/net/websocket/websocket_test.go deleted file mode 100644 index a376aba..0000000 --- a/_third_party/golang.org/x/net/websocket/websocket_test.go +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bytes" - "fmt" - "io" - "log" - "net" - "net/http" - "net/http/httptest" - "net/url" - "strings" - "sync" - "testing" -) - -var serverAddr string -var once sync.Once - -func echoServer(ws *Conn) { io.Copy(ws, ws) } - -type Count struct { - S string - N int -} - -func countServer(ws *Conn) { - for { - var count Count - err := JSON.Receive(ws, &count) - if err != nil { - return - } - count.N++ - count.S = strings.Repeat(count.S, count.N) - err = JSON.Send(ws, count) - if err != nil { - return - } - } -} - -func subProtocolHandshake(config *Config, req *http.Request) error { - for _, proto := range config.Protocol { - if proto == "chat" { - config.Protocol = []string{proto} - return nil - } - } - return ErrBadWebSocketProtocol -} - -func subProtoServer(ws *Conn) { - for _, proto := range ws.Config().Protocol { - io.WriteString(ws, proto) - } -} - -func startServer() { - http.Handle("/echo", Handler(echoServer)) - http.Handle("/count", Handler(countServer)) - subproto := Server{ - Handshake: subProtocolHandshake, - Handler: Handler(subProtoServer), - } - http.Handle("/subproto", subproto) - server := httptest.NewServer(nil) - serverAddr = server.Listener.Addr().String() - log.Print("Test WebSocket server listening on ", serverAddr) -} - -func newConfig(t *testing.T, path string) *Config { - config, _ := NewConfig(fmt.Sprintf("ws://%s%s", serverAddr, path), "http://localhost") - return config -} - -func TestEcho(t *testing.T) { - once.Do(startServer) - - // websocket.Dial() - client, err := net.Dial("tcp", serverAddr) - if err != nil { - t.Fatal("dialing", err) - } - conn, err := NewClient(newConfig(t, "/echo"), client) - if err != nil { - t.Errorf("WebSocket handshake error: %v", err) - return - } - - msg := []byte("hello, world\n") - if _, err := conn.Write(msg); err != nil { - t.Errorf("Write: %v", err) - } - var actual_msg = make([]byte, 512) - n, err := conn.Read(actual_msg) - if err != nil { - t.Errorf("Read: %v", err) - } - actual_msg = actual_msg[0:n] - if !bytes.Equal(msg, actual_msg) { - t.Errorf("Echo: expected %q got %q", msg, actual_msg) - } - conn.Close() -} - -func TestAddr(t *testing.T) { - once.Do(startServer) - - // websocket.Dial() - client, err := net.Dial("tcp", serverAddr) - if err != nil { - t.Fatal("dialing", err) - } - conn, err := NewClient(newConfig(t, "/echo"), client) - if err != nil { - t.Errorf("WebSocket handshake error: %v", err) - return - } - - ra := conn.RemoteAddr().String() - if !strings.HasPrefix(ra, "ws://") || !strings.HasSuffix(ra, "/echo") { - t.Errorf("Bad remote addr: %v", ra) - } - la := conn.LocalAddr().String() - if !strings.HasPrefix(la, "http://") { - t.Errorf("Bad local addr: %v", la) - } - conn.Close() -} - -func TestCount(t *testing.T) { - once.Do(startServer) - - // websocket.Dial() - client, err := net.Dial("tcp", serverAddr) - if err != nil { - t.Fatal("dialing", err) - } - conn, err := NewClient(newConfig(t, "/count"), client) - if err != nil { - t.Errorf("WebSocket handshake error: %v", err) - return - } - - var count Count - count.S = "hello" - if err := JSON.Send(conn, count); err != nil { - t.Errorf("Write: %v", err) - } - if err := JSON.Receive(conn, &count); err != nil { - t.Errorf("Read: %v", err) - } - if count.N != 1 { - t.Errorf("count: expected %d got %d", 1, count.N) - } - if count.S != "hello" { - t.Errorf("count: expected %q got %q", "hello", count.S) - } - if err := JSON.Send(conn, count); err != nil { - t.Errorf("Write: %v", err) - } - if err := JSON.Receive(conn, &count); err != nil { - t.Errorf("Read: %v", err) - } - if count.N != 2 { - t.Errorf("count: expected %d got %d", 2, count.N) - } - if count.S != "hellohello" { - t.Errorf("count: expected %q got %q", "hellohello", count.S) - } - conn.Close() -} - -func TestWithQuery(t *testing.T) { - once.Do(startServer) - - client, err := net.Dial("tcp", serverAddr) - if err != nil { - t.Fatal("dialing", err) - } - - config := newConfig(t, "/echo") - config.Location, err = url.ParseRequestURI(fmt.Sprintf("ws://%s/echo?q=v", serverAddr)) - if err != nil { - t.Fatal("location url", err) - } - - ws, err := NewClient(config, client) - if err != nil { - t.Errorf("WebSocket handshake: %v", err) - return - } - ws.Close() -} - -func testWithProtocol(t *testing.T, subproto []string) (string, error) { - once.Do(startServer) - - client, err := net.Dial("tcp", serverAddr) - if err != nil { - t.Fatal("dialing", err) - } - - config := newConfig(t, "/subproto") - config.Protocol = subproto - - ws, err := NewClient(config, client) - if err != nil { - return "", err - } - msg := make([]byte, 16) - n, err := ws.Read(msg) - if err != nil { - return "", err - } - ws.Close() - return string(msg[:n]), nil -} - -func TestWithProtocol(t *testing.T) { - proto, err := testWithProtocol(t, []string{"chat"}) - if err != nil { - t.Errorf("SubProto: unexpected error: %v", err) - } - if proto != "chat" { - t.Errorf("SubProto: expected %q, got %q", "chat", proto) - } -} - -func TestWithTwoProtocol(t *testing.T) { - proto, err := testWithProtocol(t, []string{"test", "chat"}) - if err != nil { - t.Errorf("SubProto: unexpected error: %v", err) - } - if proto != "chat" { - t.Errorf("SubProto: expected %q, got %q", "chat", proto) - } -} - -func TestWithBadProtocol(t *testing.T) { - _, err := testWithProtocol(t, []string{"test"}) - if err != ErrBadStatus { - t.Errorf("SubProto: expected %v, got %v", ErrBadStatus, err) - } -} - -func TestHTTP(t *testing.T) { - once.Do(startServer) - - // If the client did not send a handshake that matches the protocol - // specification, the server MUST return an HTTP response with an - // appropriate error code (such as 400 Bad Request) - resp, err := http.Get(fmt.Sprintf("http://%s/echo", serverAddr)) - if err != nil { - t.Errorf("Get: error %#v", err) - return - } - if resp == nil { - t.Error("Get: resp is null") - return - } - if resp.StatusCode != http.StatusBadRequest { - t.Errorf("Get: expected %q got %q", http.StatusBadRequest, resp.StatusCode) - } -} - -func TestTrailingSpaces(t *testing.T) { - // http://code.google.com/p/go/issues/detail?id=955 - // The last runs of this create keys with trailing spaces that should not be - // generated by the client. - once.Do(startServer) - config := newConfig(t, "/echo") - for i := 0; i < 30; i++ { - // body - ws, err := DialConfig(config) - if err != nil { - t.Errorf("Dial #%d failed: %v", i, err) - break - } - ws.Close() - } -} - -func TestDialConfigBadVersion(t *testing.T) { - once.Do(startServer) - config := newConfig(t, "/echo") - config.Version = 1234 - - _, err := DialConfig(config) - - if dialerr, ok := err.(*DialError); ok { - if dialerr.Err != ErrBadProtocolVersion { - t.Errorf("dial expected err %q but got %q", ErrBadProtocolVersion, dialerr.Err) - } - } -} - -func TestSmallBuffer(t *testing.T) { - // http://code.google.com/p/go/issues/detail?id=1145 - // Read should be able to handle reading a fragment of a frame. - once.Do(startServer) - - // websocket.Dial() - client, err := net.Dial("tcp", serverAddr) - if err != nil { - t.Fatal("dialing", err) - } - conn, err := NewClient(newConfig(t, "/echo"), client) - if err != nil { - t.Errorf("WebSocket handshake error: %v", err) - return - } - - msg := []byte("hello, world\n") - if _, err := conn.Write(msg); err != nil { - t.Errorf("Write: %v", err) - } - var small_msg = make([]byte, 8) - n, err := conn.Read(small_msg) - if err != nil { - t.Errorf("Read: %v", err) - } - if !bytes.Equal(msg[:len(small_msg)], small_msg) { - t.Errorf("Echo: expected %q got %q", msg[:len(small_msg)], small_msg) - } - var second_msg = make([]byte, len(msg)) - n, err = conn.Read(second_msg) - if err != nil { - t.Errorf("Read: %v", err) - } - second_msg = second_msg[0:n] - if !bytes.Equal(msg[len(small_msg):], second_msg) { - t.Errorf("Echo: expected %q got %q", msg[len(small_msg):], second_msg) - } - conn.Close() -} - -var parseAuthorityTests = []struct { - in *url.URL - out string -}{ - { - &url.URL{ - Scheme: "ws", - Host: "www.google.com", - }, - "www.google.com:80", - }, - { - &url.URL{ - Scheme: "wss", - Host: "www.google.com", - }, - "www.google.com:443", - }, - { - &url.URL{ - Scheme: "ws", - Host: "www.google.com:80", - }, - "www.google.com:80", - }, - { - &url.URL{ - Scheme: "wss", - Host: "www.google.com:443", - }, - "www.google.com:443", - }, - // some invalid ones for parseAuthority. parseAuthority doesn't - // concern itself with the scheme unless it actually knows about it - { - &url.URL{ - Scheme: "http", - Host: "www.google.com", - }, - "www.google.com", - }, - { - &url.URL{ - Scheme: "http", - Host: "www.google.com:80", - }, - "www.google.com:80", - }, - { - &url.URL{ - Scheme: "asdf", - Host: "127.0.0.1", - }, - "127.0.0.1", - }, - { - &url.URL{ - Scheme: "asdf", - Host: "www.google.com", - }, - "www.google.com", - }, -} - -func TestParseAuthority(t *testing.T) { - for _, tt := range parseAuthorityTests { - out := parseAuthority(tt.in) - if out != tt.out { - t.Errorf("got %v; want %v", out, tt.out) - } - } -} diff --git a/_third_party/golang.org/x/oauth2/AUTHORS b/_third_party/golang.org/x/oauth2/AUTHORS deleted file mode 100644 index 15167cd..0000000 --- a/_third_party/golang.org/x/oauth2/AUTHORS +++ /dev/null @@ -1,3 +0,0 @@ -# This source code refers to The Go Authors for copyright purposes. -# The master list of authors is in the main Go distribution, -# visible at http://tip.golang.org/AUTHORS. diff --git a/_third_party/golang.org/x/oauth2/CONTRIBUTING.md b/_third_party/golang.org/x/oauth2/CONTRIBUTING.md deleted file mode 100644 index d76faef..0000000 --- a/_third_party/golang.org/x/oauth2/CONTRIBUTING.md +++ /dev/null @@ -1,25 +0,0 @@ -# Contributing - -We don't use GitHub pull requests but use Gerrit for code reviews, -similar to the Go project. - -1. Sign one of the contributor license agreements below. -2. `go get golang.org/x/review/git-codereview` to install the code reviewing tool. -3. Get the package by running `go get -d golang.org/x/oauth2`. -Make changes and create a change by running `git codereview change `, provide a command message, and use `git codereview mail` to create a Gerrit CL. -Keep amending to the change and mail as your recieve feedback. - -For more information about the workflow, see Go's [Contribution Guidelines](https://golang.org/doc/contribute.html). - -Before we can accept any pull requests -we have to jump through a couple of legal hurdles, -primarily a Contributor License Agreement (CLA): - -- **If you are an individual writing original source code** - and you're sure you own the intellectual property, - then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html). -- **If you work for a company that wants to allow you to contribute your work**, - then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html). - -You can sign these electronically (just scroll to the bottom). -After that, we'll be able to accept your pull requests. diff --git a/_third_party/golang.org/x/oauth2/CONTRIBUTORS b/_third_party/golang.org/x/oauth2/CONTRIBUTORS deleted file mode 100644 index 1c4577e..0000000 --- a/_third_party/golang.org/x/oauth2/CONTRIBUTORS +++ /dev/null @@ -1,3 +0,0 @@ -# This source code was written by the Go contributors. -# The master list of contributors is in the main Go distribution, -# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/_third_party/golang.org/x/oauth2/LICENSE b/_third_party/golang.org/x/oauth2/LICENSE deleted file mode 100644 index d02f24f..0000000 --- a/_third_party/golang.org/x/oauth2/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The oauth2 Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/_third_party/golang.org/x/oauth2/README.md b/_third_party/golang.org/x/oauth2/README.md deleted file mode 100644 index ecf9c4e..0000000 --- a/_third_party/golang.org/x/oauth2/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# OAuth2 for Go - -[![Build Status](https://travis-ci.org/golang/oauth2.svg?branch=master)](https://travis-ci.org/golang/oauth2) - -oauth2 package contains a client implementation for OAuth 2.0 spec. - -## Installation - -~~~~ -go get golang.org/x/oauth2 -~~~~ - -See godoc for further documentation and examples. - -* [godoc.org/golang.org/x/oauth2](http://godoc.org/golang.org/x/oauth2) -* [godoc.org/golang.org/x/oauth2/google](http://godoc.org/golang.org/x/oauth2/google) - - diff --git a/_third_party/golang.org/x/oauth2/client_appengine.go b/_third_party/golang.org/x/oauth2/client_appengine.go index d9ce804..7286f58 100644 --- a/_third_party/golang.org/x/oauth2/client_appengine.go +++ b/_third_party/golang.org/x/oauth2/client_appengine.go @@ -2,38 +2,23 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build appengine,!appenginevm +// +build appengine appenginevm // App Engine hooks. package oauth2 import ( - "log" "net/http" - "sync" - "appengine" - "appengine/urlfetch" + "github.com/mjibson/mog/_third_party/golang.org/x/net/context" + "github.com/mjibson/mog/_third_party/google.golang.org/appengine/urlfetch" ) -var warnOnce sync.Once - func init() { registerContextClientFunc(contextClientAppEngine) } -func contextClientAppEngine(ctx Context) (*http.Client, error) { - if actx, ok := ctx.(appengine.Context); ok { - return urlfetch.Client(actx), nil - } - // The user did it wrong. We'll log once (and hope they see it - // in dev_appserver), but stil return (nil, nil) in case some - // other contextClientFunc hook finds a way to proceed. - warnOnce.Do(gaeDoingItWrongHelp) - return nil, nil -} - -func gaeDoingItWrongHelp() { - log.Printf("WARNING: you attempted to use the oauth2 package without passing a valid appengine.Context or *http.Request as the oauth2.Context. App Engine requires that all service RPCs (including urlfetch) be associated with an *http.Request/appengine.Context.") +func contextClientAppEngine(ctx context.Context) (*http.Client, error) { + return urlfetch.Client(ctx), nil } diff --git a/_third_party/golang.org/x/oauth2/example_test.go b/_third_party/golang.org/x/oauth2/example_test.go deleted file mode 100644 index a9ba453..0000000 --- a/_third_party/golang.org/x/oauth2/example_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2014 The oauth2 Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package oauth2_test - -import ( - "fmt" - "log" - "testing" - - "github.com/mjibson/mog/_third_party/golang.org/x/oauth2" -) - -// TODO(jbd): Remove after Go 1.4. -// Related to https://codereview.appspot.com/107320046 -func TestA(t *testing.T) {} - -func ExampleConfig() { - conf := &oauth2.Config{ - ClientID: "YOUR_CLIENT_ID", - ClientSecret: "YOUR_CLIENT_SECRET", - Scopes: []string{"SCOPE1", "SCOPE2"}, - Endpoint: oauth2.Endpoint{ - AuthURL: "https://provider.com/o/oauth2/auth", - TokenURL: "https://provider.com/o/oauth2/token", - }, - } - - // Redirect user to consent page to ask for permission - // for the scopes specified above. - url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline) - fmt.Printf("Visit the URL for the auth dialog: %v", url) - - // Use the authorization code that is pushed to the redirect URL. - // NewTransportWithCode will do the handshake to retrieve - // an access token and initiate a Transport that is - // authorized and authenticated by the retrieved token. - var code string - if _, err := fmt.Scan(&code); err != nil { - log.Fatal(err) - } - tok, err := conf.Exchange(oauth2.NoContext, code) - if err != nil { - log.Fatal(err) - } - - client := conf.Client(oauth2.NoContext, tok) - client.Get("...") -} diff --git a/_third_party/golang.org/x/oauth2/google/appengine.go b/_third_party/golang.org/x/oauth2/google/appengine.go index f0c800c..9a085d8 100644 --- a/_third_party/golang.org/x/oauth2/google/appengine.go +++ b/_third_party/golang.org/x/oauth2/google/appengine.go @@ -2,36 +2,82 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build appengine - package google import ( + "sort" + "strings" + "sync" "time" - "appengine" - + "github.com/mjibson/mog/_third_party/golang.org/x/net/context" "github.com/mjibson/mog/_third_party/golang.org/x/oauth2" ) +// Set at init time by appengine_hook.go. If nil, we're not on App Engine. +var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error) + // AppEngineTokenSource returns a token source that fetches tokens // issued to the current App Engine application's service account. // If you are implementing a 3-legged OAuth 2.0 flow on App Engine // that involves user accounts, see oauth2.Config instead. // // The provided context must have come from appengine.NewContext. -func AppEngineTokenSource(ctx oauth2.Context, scope ...string) oauth2.TokenSource { +func AppEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource { + if appengineTokenFunc == nil { + panic("google: AppEngineTokenSource can only be used on App Engine.") + } + scopes := append([]string{}, scope...) + sort.Strings(scopes) return &appEngineTokenSource{ - ctx: ctx, - scopes: scope, - fetcherFunc: aeFetcherFunc, + ctx: ctx, + scopes: scopes, + key: strings.Join(scopes, " "), } } -var aeFetcherFunc = func(ctx oauth2.Context, scope ...string) (string, time.Time, error) { - c, ok := ctx.(appengine.Context) +// aeTokens helps the fetched tokens to be reused until their expiration. +var ( + aeTokensMu sync.Mutex + aeTokens = make(map[string]*tokenLock) // key is space-separated scopes +) + +type tokenLock struct { + mu sync.Mutex // guards t; held while fetching or updating t + t *oauth2.Token +} + +type appEngineTokenSource struct { + ctx context.Context + scopes []string + key string // to aeTokens map; space-separated scopes +} + +func (ts *appEngineTokenSource) Token() (*oauth2.Token, error) { + if appengineTokenFunc == nil { + panic("google: AppEngineTokenSource can only be used on App Engine.") + } + + aeTokensMu.Lock() + tok, ok := aeTokens[ts.key] if !ok { - return "", time.Time{}, errInvalidContext + tok = &tokenLock{} + aeTokens[ts.key] = tok + } + aeTokensMu.Unlock() + + tok.mu.Lock() + defer tok.mu.Unlock() + if tok.t.Valid() { + return tok.t, nil + } + access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...) + if err != nil { + return nil, err + } + tok.t = &oauth2.Token{ + AccessToken: access, + Expiry: exp, } - return appengine.AccessToken(c, scope...) + return tok.t, nil } diff --git a/_third_party/golang.org/x/oauth2/google/appengine_hook.go b/_third_party/golang.org/x/oauth2/google/appengine_hook.go new file mode 100644 index 0000000..54ec739 --- /dev/null +++ b/_third_party/golang.org/x/oauth2/google/appengine_hook.go @@ -0,0 +1,13 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appengine appenginevm + +package google + +import "github.com/mjibson/mog/_third_party/google.golang.org/appengine" + +func init() { + appengineTokenFunc = appengine.AccessToken +} diff --git a/_third_party/golang.org/x/oauth2/google/appenginedoc.go b/_third_party/golang.org/x/oauth2/google/appenginedoc.go deleted file mode 100644 index dc79129..0000000 --- a/_third_party/golang.org/x/oauth2/google/appenginedoc.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2015 The oauth2 Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !appengine,!appenginevm - -package google - -import "github.com/mjibson/mog/_third_party/golang.org/x/oauth2" - -// AppEngineTokenSource returns a token source that fetches tokens -// issued to the current App Engine application's service account. -// If you are implementing a 3-legged OAuth 2.0 flow on App Engine -// that involves user accounts, see oauth2.Config instead. -// -// You are required to provide a valid appengine.Context as context. -func AppEngineTokenSource(ctx oauth2.Context, scope ...string) oauth2.TokenSource { - panic("You should only use an AppEngineTokenSource in an App Engine application.") -} diff --git a/_third_party/golang.org/x/oauth2/google/appenginevm.go b/_third_party/golang.org/x/oauth2/google/appenginevm.go deleted file mode 100644 index e895922..0000000 --- a/_third_party/golang.org/x/oauth2/google/appenginevm.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2014 The oauth2 Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build appenginevm - -package google - -import ( - "time" - - "github.com/mjibson/mog/_third_party/golang.org/x/net/context" - "github.com/mjibson/mog/_third_party/golang.org/x/oauth2" - "github.com/mjibson/mog/_third_party/google.golang.org/appengine" -) - -// AppEngineTokenSource returns a token source that fetches tokens -// issued to the current App Engine application's service account. -// If you are implementing a 3-legged OAuth 2.0 flow on App Engine -// that involves user accounts, see oauth2.Config instead. -// -// The provided context must have come from appengine.NewContext. -func AppEngineTokenSource(ctx oauth2.Context, scope ...string) oauth2.TokenSource { - return &appEngineTokenSource{ - ctx: ctx, - scopes: scope, - fetcherFunc: aeVMFetcherFunc, - } -} - -var aeVMFetcherFunc = func(ctx oauth2.Context, scope ...string) (string, time.Time, error) { - c, ok := ctx.(context.Context) - if !ok { - return "", time.Time{}, errInvalidContext - } - return appengine.AccessToken(c, scope...) -} diff --git a/_third_party/golang.org/x/oauth2/google/default.go b/_third_party/golang.org/x/oauth2/google/default.go new file mode 100644 index 0000000..6fd3be8 --- /dev/null +++ b/_third_party/golang.org/x/oauth2/google/default.go @@ -0,0 +1,154 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "runtime" + + "github.com/mjibson/mog/_third_party/golang.org/x/net/context" + "github.com/mjibson/mog/_third_party/golang.org/x/oauth2" + "github.com/mjibson/mog/_third_party/golang.org/x/oauth2/jwt" + "github.com/mjibson/mog/_third_party/google.golang.org/cloud/compute/metadata" +) + +// DefaultClient returns an HTTP Client that uses the +// DefaultTokenSource to obtain authentication credentials. +// +// This client should be used when developing services +// that run on Google App Engine or Google Compute Engine +// and use "Application Default Credentials." +// +// For more details, see: +// https://developers.google.com/accounts/docs/application-default-credentials +// +func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) { + ts, err := DefaultTokenSource(ctx, scope...) + if err != nil { + return nil, err + } + return oauth2.NewClient(ctx, ts), nil +} + +// DefaultTokenSource is a token source that uses +// "Application Default Credentials". +// +// It looks for credentials in the following places, +// preferring the first location found: +// +// 1. A JSON file whose path is specified by the +// GOOGLE_APPLICATION_CREDENTIALS environment variable. +// 2. A JSON file in a location known to the gcloud command-line tool. +// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json. +// On other systems, $HOME/.config/gcloud/application_default_credentials.json. +// 3. On Google App Engine it uses the appengine.AccessToken function. +// 4. On Google Compute Engine, it fetches credentials from the metadata server. +// (In this final case any provided scopes are ignored.) +// +// For more details, see: +// https://developers.google.com/accounts/docs/application-default-credentials +// +func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) { + // First, try the environment variable. + const envVar = "GOOGLE_APPLICATION_CREDENTIALS" + if filename := os.Getenv(envVar); filename != "" { + ts, err := tokenSourceFromFile(ctx, filename, scope) + if err != nil { + return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err) + } + return ts, nil + } + + // Second, try a well-known file. + filename := wellKnownFile() + _, err := os.Stat(filename) + if err == nil { + ts, err2 := tokenSourceFromFile(ctx, filename, scope) + if err2 == nil { + return ts, nil + } + err = err2 + } else if os.IsNotExist(err) { + err = nil // ignore this error + } + if err != nil { + return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err) + } + + // Third, if we're on Google App Engine use those credentials. + if appengineTokenFunc != nil { + return AppEngineTokenSource(ctx, scope...), nil + } + + // Fourth, if we're on Google Compute Engine use the metadata server. + if metadata.OnGCE() { + return ComputeTokenSource(""), nil + } + + // None are found; return helpful error. + const url = "https://developers.google.com/accounts/docs/application-default-credentials" + return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url) +} + +func wellKnownFile() string { + const f = "application_default_credentials.json" + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("APPDATA"), "gcloud", f) + } + return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f) +} + +func tokenSourceFromFile(ctx context.Context, filename string, scopes []string) (oauth2.TokenSource, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + var d struct { + // Common fields + Type string + ClientID string `json:"client_id"` + + // User Credential fields + ClientSecret string `json:"client_secret"` + RefreshToken string `json:"refresh_token"` + + // Service Account fields + ClientEmail string `json:"client_email"` + PrivateKeyID string `json:"private_key_id"` + PrivateKey string `json:"private_key"` + } + if err := json.Unmarshal(b, &d); err != nil { + return nil, err + } + switch d.Type { + case "authorized_user": + cfg := &oauth2.Config{ + ClientID: d.ClientID, + ClientSecret: d.ClientSecret, + Scopes: append([]string{}, scopes...), // copy + Endpoint: Endpoint, + } + tok := &oauth2.Token{RefreshToken: d.RefreshToken} + return cfg.TokenSource(ctx, tok), nil + case "service_account": + cfg := &jwt.Config{ + Email: d.ClientEmail, + PrivateKey: []byte(d.PrivateKey), + Scopes: append([]string{}, scopes...), // copy + TokenURL: JWTTokenURL, + } + return cfg.TokenSource(ctx), nil + case "": + return nil, errors.New("missing 'type' field in credentials") + default: + return nil, fmt.Errorf("unknown credential type: %q", d.Type) + } +} diff --git a/_third_party/golang.org/x/oauth2/google/example_test.go b/_third_party/golang.org/x/oauth2/google/example_test.go deleted file mode 100644 index c0a68c9..0000000 --- a/_third_party/golang.org/x/oauth2/google/example_test.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2014 The oauth2 Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build appenginevm !appengine - -package google_test - -import ( - "fmt" - "io/ioutil" - "log" - "net/http" - "testing" - - "github.com/mjibson/mog/_third_party/golang.org/x/oauth2" - "github.com/mjibson/mog/_third_party/golang.org/x/oauth2/google" - "github.com/mjibson/mog/_third_party/golang.org/x/oauth2/jwt" - "github.com/mjibson/mog/_third_party/google.golang.org/appengine" - "github.com/mjibson/mog/_third_party/google.golang.org/appengine/urlfetch" -) - -// Remove after Go 1.4. -// Related to https://codereview.appspot.com/107320046 -func TestA(t *testing.T) {} - -func Example_webServer() { - // Your credentials should be obtained from the Google - // Developer Console (https://console.developers.google.com). - conf := &oauth2.Config{ - ClientID: "YOUR_CLIENT_ID", - ClientSecret: "YOUR_CLIENT_SECRET", - RedirectURL: "YOUR_REDIRECT_URL", - Scopes: []string{ - "https://www.googleapis.com/auth/bigquery", - "https://www.googleapis.com/auth/blogger", - }, - Endpoint: google.Endpoint, - } - // Redirect user to Google's consent page to ask for permission - // for the scopes specified above. - url := conf.AuthCodeURL("state") - fmt.Printf("Visit the URL for the auth dialog: %v", url) - - // Handle the exchange code to initiate a transport. - tok, err := conf.Exchange(oauth2.NoContext, "authorization-code") - if err != nil { - log.Fatal(err) - } - client := conf.Client(oauth2.NoContext, tok) - client.Get("...") -} - -func ExampleJWTConfigFromJSON() { - // Your credentials should be obtained from the Google - // Developer Console (https://console.developers.google.com). - // Navigate to your project, then see the "Credentials" page - // under "APIs & Auth". - // To create a service account client, click "Create new Client ID", - // select "Service Account", and click "Create Client ID". A JSON - // key file will then be downloaded to your computer. - data, err := ioutil.ReadFile("/path/to/your-project-key.json") - if err != nil { - log.Fatal(err) - } - conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/bigquery") - if err != nil { - log.Fatal(err) - } - // Initiate an http.Client. The following GET request will be - // authorized and authenticated on the behalf of - // your service account. - client := conf.Client(oauth2.NoContext) - client.Get("...") -} - -func ExampleSDKConfig() { - // The credentials will be obtained from the first account that - // has been authorized with `gcloud auth login`. - conf, err := google.NewSDKConfig("") - if err != nil { - log.Fatal(err) - } - // Initiate an http.Client. The following GET request will be - // authorized and authenticated on the behalf of the SDK user. - client := conf.Client(oauth2.NoContext) - client.Get("...") -} - -func Example_serviceAccount() { - // Your credentials should be obtained from the Google - // Developer Console (https://console.developers.google.com). - conf := &jwt.Config{ - Email: "xxx@developer.gserviceaccount.com", - // The contents of your RSA private key or your PEM file - // that contains a private key. - // If you have a p12 file instead, you - // can use `openssl` to export the private key into a pem file. - // - // $ openssl pkcs12 -in key.p12 -passin pass:notasecret -out key.pem -nodes - // - // The field only supports PEM containers with no passphrase. - // The openssl command will convert p12 keys to passphrase-less PEM containers. - PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."), - Scopes: []string{ - "https://www.googleapis.com/auth/bigquery", - "https://www.googleapis.com/auth/blogger", - }, - TokenURL: google.JWTTokenURL, - // If you would like to impersonate a user, you can - // create a transport with a subject. The following GET - // request will be made on the behalf of user@example.com. - // Optional. - Subject: "user@example.com", - } - // Initiate an http.Client, the following GET request will be - // authorized and authenticated on the behalf of user@example.com. - client := conf.Client(oauth2.NoContext) - client.Get("...") -} - -func ExampleAppEngineTokenSource() { - var req *http.Request // from the ServeHTTP handler - ctx := appengine.NewContext(req) - client := &http.Client{ - Transport: &oauth2.Transport{ - Source: google.AppEngineTokenSource(ctx, "https://www.googleapis.com/auth/bigquery"), - Base: &urlfetch.Transport{ - Context: ctx, - }, - }, - } - client.Get("...") -} - -func ExampleComputeTokenSource() { - client := &http.Client{ - Transport: &oauth2.Transport{ - // Fetch from Google Compute Engine's metadata server to retrieve - // an access token for the provided account. - // If no account is specified, "default" is used. - Source: google.ComputeTokenSource(""), - }, - } - client.Get("...") -} diff --git a/_third_party/golang.org/x/oauth2/google/google.go b/_third_party/golang.org/x/oauth2/google/google.go index b32eec0..41f0ed3 100644 --- a/_third_party/golang.org/x/oauth2/google/google.go +++ b/_third_party/golang.org/x/oauth2/google/google.go @@ -2,15 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package google provides support for making -// OAuth2 authorized and authenticated HTTP requests -// to Google APIs. It supports Web server, client-side, -// service accounts, Google Compute Engine service accounts, -// and Google App Engine service accounts authorization -// and authentications flows: +// Package google provides support for making OAuth2 authorized and +// authenticated HTTP requests to Google APIs. +// It supports the Web server flow, client-side credentials, service accounts, +// Google Compute Engine service accounts, and Google App Engine service +// accounts. // // For more information, please read -// https://developers.google.com/accounts/docs/OAuth2. +// https://developers.google.com/accounts/docs/OAuth2 +// and +// https://developers.google.com/accounts/docs/application-default-credentials. package google // import "github.com/mjibson/mog/_third_party/golang.org/x/oauth2/google" import ( @@ -25,9 +26,6 @@ import ( "github.com/mjibson/mog/_third_party/google.golang.org/cloud/compute/metadata" ) -// TODO(bradfitz,jbd): import "google.golang.org/cloud/compute/metadata" instead of -// the metaClient and metadata.google.internal stuff below. - // Endpoint is Google's OAuth 2.0 endpoint. var Endpoint = oauth2.Endpoint{ AuthURL: "https://accounts.google.com/o/oauth2/auth", @@ -37,6 +35,50 @@ var Endpoint = oauth2.Endpoint{ // JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow. const JWTTokenURL = "https://accounts.google.com/o/oauth2/token" +// ConfigFromJSON uses a Google Developers Console client_credentials.json +// file to construct a config. +// client_credentials.json can be downloadable from https://console.developers.google.com, +// under "APIs & Auth" > "Credentials". Download the Web application credentials in the +// JSON format and provide the contents of the file as jsonKey. +func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) { + type cred struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + RedirectURIs []string `json:"redirect_uris"` + AuthURI string `json:"auth_uri"` + TokenURI string `json:"token_uri"` + } + var j struct { + Web *cred `json:"web"` + Installed *cred `json:"installed"` + } + if err := json.Unmarshal(jsonKey, &j); err != nil { + return nil, err + } + var c *cred + switch { + case j.Web != nil: + c = j.Web + case j.Installed != nil: + c = j.Installed + default: + return nil, fmt.Errorf("oauth2/google: no credentials found") + } + if len(c.RedirectURIs) < 1 { + return nil, errors.New("oauth2/google: missing redirect URL in the client_credentials.json") + } + return &oauth2.Config{ + ClientID: c.ClientID, + ClientSecret: c.ClientSecret, + RedirectURL: c.RedirectURIs[0], + Scopes: scope, + Endpoint: oauth2.Endpoint{ + AuthURL: c.AuthURI, + TokenURL: c.TokenURI, + }, + }, nil +} + // JWTConfigFromJSON uses a Google Developers service account JSON key file to read // the credentials that authorize and authenticate the requests. // Create a service account on "Credentials" page under "APIs & Auth" for your diff --git a/_third_party/golang.org/x/oauth2/google/sdk.go b/_third_party/golang.org/x/oauth2/google/sdk.go index 0566212..7e6306f 100644 --- a/_third_party/golang.org/x/oauth2/google/sdk.go +++ b/_third_party/golang.org/x/oauth2/google/sdk.go @@ -6,6 +6,7 @@ package google import ( "encoding/json" + "errors" "fmt" "net/http" "os" @@ -15,6 +16,7 @@ import ( "strings" "time" + "github.com/mjibson/mog/_third_party/golang.org/x/net/context" "github.com/mjibson/mog/_third_party/golang.org/x/oauth2" "github.com/mjibson/mog/_third_party/golang.org/x/oauth2/internal" ) @@ -22,11 +24,11 @@ import ( type sdkCredentials struct { Data []struct { Credential struct { - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - TokenExpiry time.Time `json:"token_expiry"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + TokenExpiry *time.Time `json:"token_expiry"` } `json:"credential"` Key struct { Account string `json:"account"` @@ -91,6 +93,13 @@ func NewSDKConfig(account string) (*SDKConfig, error) { for _, d := range c.Data { if account == "" || d.Key.Account == account { + if d.Credential.AccessToken == "" && d.Credential.RefreshToken == "" { + return nil, fmt.Errorf("oauth2/google: no token available for account %q", account) + } + var expiry time.Time + if d.Credential.TokenExpiry != nil { + expiry = *d.Credential.TokenExpiry + } return &SDKConfig{ conf: oauth2.Config{ ClientID: d.Credential.ClientID, @@ -102,7 +111,7 @@ func NewSDKConfig(account string) (*SDKConfig, error) { initialToken: &oauth2.Token{ AccessToken: d.Credential.AccessToken, RefreshToken: d.Credential.RefreshToken, - Expiry: d.Credential.TokenExpiry, + Expiry: expiry, }, }, nil } @@ -115,7 +124,7 @@ func NewSDKConfig(account string) (*SDKConfig, error) { // underlying http.RoundTripper will be obtained using the provided // context. The returned client and its Transport should not be // modified. -func (c *SDKConfig) Client(ctx oauth2.Context) *http.Client { +func (c *SDKConfig) Client(ctx context.Context) *http.Client { return &http.Client{ Transport: &oauth2.Transport{ Source: c.TokenSource(ctx), @@ -128,7 +137,7 @@ func (c *SDKConfig) Client(ctx oauth2.Context) *http.Client { // It will returns the current access token stored in the credentials, // and refresh it when it expires, but it won't update the credentials // with the new access token. -func (c *SDKConfig) TokenSource(ctx oauth2.Context) oauth2.TokenSource { +func (c *SDKConfig) TokenSource(ctx context.Context) oauth2.TokenSource { return c.conf.TokenSource(ctx, c.initialToken) } @@ -137,23 +146,20 @@ func (c *SDKConfig) Scopes() []string { return c.conf.Scopes } -func sdkConfigPath() (string, error) { +// sdkConfigPath tries to guess where the gcloud config is located. +// It can be overridden during tests. +var sdkConfigPath = func() (string, error) { if runtime.GOOS == "windows" { return filepath.Join(os.Getenv("APPDATA"), "gcloud"), nil } - unixHomeDir = guessUnixHomeDir() - if unixHomeDir == "" { - return "", fmt.Errorf("unable to get current user home directory: os/user lookup failed; $HOME is empty") + homeDir := guessUnixHomeDir() + if homeDir == "" { + return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty") } - return filepath.Join(unixHomeDir, ".config", "gcloud"), nil + return filepath.Join(homeDir, ".config", "gcloud"), nil } -var unixHomeDir string - func guessUnixHomeDir() string { - if unixHomeDir != "" { - return unixHomeDir - } usr, err := user.Current() if err == nil { return usr.HomeDir diff --git a/_third_party/golang.org/x/oauth2/google/sdk_test.go b/_third_party/golang.org/x/oauth2/google/sdk_test.go deleted file mode 100644 index 43090b6..0000000 --- a/_third_party/golang.org/x/oauth2/google/sdk_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2015 The oauth2 Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package google - -import "testing" - -func TestSDKConfig(t *testing.T) { - unixHomeDir = "testdata" - tests := []struct { - account string - accessToken string - err bool - }{ - {"", "bar_access_token", false}, - {"foo@example.com", "foo_access_token", false}, - {"bar@example.com", "bar_access_token", false}, - } - for _, tt := range tests { - c, err := NewSDKConfig(tt.account) - if (err != nil) != tt.err { - if !tt.err { - t.Errorf("expected no error, got error: %v", tt.err, err) - } else { - t.Errorf("execcted error, got none") - } - continue - } - tok := c.initialToken - if tok == nil { - t.Errorf("expected token %q, got: nil", tt.accessToken) - continue - } - if tok.AccessToken != tt.accessToken { - t.Errorf("expected token %q, got: %q", tt.accessToken, tok.AccessToken) - } - } -} diff --git a/_third_party/golang.org/x/oauth2/google/source_appengine.go b/_third_party/golang.org/x/oauth2/google/source_appengine.go deleted file mode 100644 index fc3cd37..0000000 --- a/_third_party/golang.org/x/oauth2/google/source_appengine.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2014 The oauth2 Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package google - -import ( - "errors" - "sort" - "strings" - "sync" - "time" - - "github.com/mjibson/mog/_third_party/golang.org/x/oauth2" -) - -var ( - aeTokensMu sync.Mutex // guards aeTokens and appEngineTokenSource.key - - // aeTokens helps the fetched tokens to be reused until their expiration. - aeTokens = make(map[string]*tokenLock) // key is '\0'-separated scopes -) - -var errInvalidContext = errors.New("oauth2: context must come from appengine.NewContext") - -type tokenLock struct { - mu sync.Mutex // guards t; held while updating t - t *oauth2.Token -} - -type appEngineTokenSource struct { - ctx oauth2.Context - - // fetcherFunc makes the actual RPC to fetch a new access - // token with an expiry time. Provider of this function is - // responsible to assert that the given context is valid. - fetcherFunc func(ctx oauth2.Context, scope ...string) (accessToken string, expiry time.Time, err error) - - // scopes and key are guarded by the package-level mutex aeTokensMu - scopes []string - key string -} - -func (ts *appEngineTokenSource) Token() (*oauth2.Token, error) { - aeTokensMu.Lock() - if ts.key == "" { - sort.Sort(sort.StringSlice(ts.scopes)) - ts.key = strings.Join(ts.scopes, string(0)) - } - tok, ok := aeTokens[ts.key] - if !ok { - tok = &tokenLock{} - aeTokens[ts.key] = tok - } - aeTokensMu.Unlock() - - tok.mu.Lock() - defer tok.mu.Unlock() - if tok.t.Valid() { - return tok.t, nil - } - access, exp, err := ts.fetcherFunc(ts.ctx, ts.scopes...) - if err != nil { - return nil, err - } - tok.t = &oauth2.Token{ - AccessToken: access, - Expiry: exp, - } - return tok.t, nil -} diff --git a/_third_party/golang.org/x/oauth2/internal/oauth2.go b/_third_party/golang.org/x/oauth2/internal/oauth2.go index aa23073..37571a1 100644 --- a/_third_party/golang.org/x/oauth2/internal/oauth2.go +++ b/_third_party/golang.org/x/oauth2/internal/oauth2.go @@ -30,12 +30,12 @@ func ParseKey(key []byte) (*rsa.PrivateKey, error) { if err != nil { parsedKey, err = x509.ParsePKCS1PrivateKey(key) if err != nil { - return nil, err + return nil, fmt.Errorf("private key should be a PEM or plain PKSC1 or PKCS8; parse error: %v", err) } } parsed, ok := parsedKey.(*rsa.PrivateKey) if !ok { - return nil, errors.New("oauth2: private key is invalid") + return nil, errors.New("private key is invalid") } return parsed, nil } diff --git a/_third_party/golang.org/x/oauth2/internal/oauth2_test.go b/_third_party/golang.org/x/oauth2/internal/oauth2_test.go deleted file mode 100644 index 014a351..0000000 --- a/_third_party/golang.org/x/oauth2/internal/oauth2_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2014 The oauth2 Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package internal contains support packages for oauth2 package. -package internal - -import ( - "reflect" - "strings" - "testing" -) - -func TestParseINI(t *testing.T) { - tests := []struct { - ini string - want map[string]map[string]string - }{ - { - `root = toor -[foo] -bar = hop -ini = nin -`, - map[string]map[string]string{ - "": map[string]string{"root": "toor"}, - "foo": map[string]string{"bar": "hop", "ini": "nin"}, - }, - }, - { - `[empty] -[section] -empty= -`, - map[string]map[string]string{ - "": map[string]string{}, - "empty": map[string]string{}, - "section": map[string]string{"empty": ""}, - }, - }, - { - `ignore -[invalid -=stuff -;comment=true -`, - map[string]map[string]string{ - "": map[string]string{}, - }, - }, - } - for _, tt := range tests { - result, err := ParseINI(strings.NewReader(tt.ini)) - if err != nil { - t.Errorf("ParseINI(%q) error %v, want: no error", tt.ini, err) - continue - } - if !reflect.DeepEqual(result, tt.want) { - t.Errorf("ParseINI(%q) = %#v, want: %#v", tt.ini, result, tt.want) - } - } -} diff --git a/_third_party/golang.org/x/oauth2/jwt/example_test.go b/_third_party/golang.org/x/oauth2/jwt/example_test.go deleted file mode 100644 index a2c67ac..0000000 --- a/_third_party/golang.org/x/oauth2/jwt/example_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2014 The oauth2 Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package jwt_test - -import ( - "github.com/mjibson/mog/_third_party/golang.org/x/oauth2" - "github.com/mjibson/mog/_third_party/golang.org/x/oauth2/jwt" -) - -func ExampleJWTConfig() { - conf := &jwt.Config{ - Email: "xxx@developer.com", - // The contents of your RSA private key or your PEM file - // that contains a private key. - // If you have a p12 file instead, you - // can use `openssl` to export the private key into a pem file. - // - // $ openssl pkcs12 -in key.p12 -out key.pem -nodes - // - // It only supports PEM containers with no passphrase. - PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."), - Subject: "user@example.com", - TokenURL: "https://provider.com/o/oauth2/token", - } - // Initiate an http.Client, the following GET request will be - // authorized and authenticated on the behalf of user@example.com. - client := conf.Client(oauth2.NoContext) - client.Get("...") -} diff --git a/_third_party/golang.org/x/oauth2/jwt/jwt.go b/_third_party/golang.org/x/oauth2/jwt/jwt.go index f447167..d5f3bcb 100644 --- a/_third_party/golang.org/x/oauth2/jwt/jwt.go +++ b/_third_party/golang.org/x/oauth2/jwt/jwt.go @@ -18,6 +18,7 @@ import ( "strings" "time" + "github.com/mjibson/mog/_third_party/golang.org/x/net/context" "github.com/mjibson/mog/_third_party/golang.org/x/oauth2" "github.com/mjibson/mog/_third_party/golang.org/x/oauth2/internal" "github.com/mjibson/mog/_third_party/golang.org/x/oauth2/jws" @@ -57,7 +58,7 @@ type Config struct { // TokenSource returns a JWT TokenSource using the configuration // in c and the HTTP client from the provided context. -func (c *Config) TokenSource(ctx oauth2.Context) oauth2.TokenSource { +func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource { return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c}) } @@ -66,14 +67,14 @@ func (c *Config) TokenSource(ctx oauth2.Context) oauth2.TokenSource { // obtained from c. // // The returned client and its Transport should not be modified. -func (c *Config) Client(ctx oauth2.Context) *http.Client { +func (c *Config) Client(ctx context.Context) *http.Client { return oauth2.NewClient(ctx, c.TokenSource(ctx)) } // jwtSource is a source that always does a signed JWT request for a token. // It should typically be wrapped with a reuseTokenSource. type jwtSource struct { - ctx oauth2.Context + ctx context.Context conf *Config } diff --git a/_third_party/golang.org/x/oauth2/jwt/jwt_test.go b/_third_party/golang.org/x/oauth2/jwt/jwt_test.go deleted file mode 100644 index 41fb041..0000000 --- a/_third_party/golang.org/x/oauth2/jwt/jwt_test.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2014 The oauth2 Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package jwt - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/mjibson/mog/_third_party/golang.org/x/oauth2" -) - -var dummyPrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAx4fm7dngEmOULNmAs1IGZ9Apfzh+BkaQ1dzkmbUgpcoghucE -DZRnAGd2aPyB6skGMXUytWQvNYav0WTR00wFtX1ohWTfv68HGXJ8QXCpyoSKSSFY -fuP9X36wBSkSX9J5DVgiuzD5VBdzUISSmapjKm+DcbRALjz6OUIPEWi1Tjl6p5RK -1w41qdbmt7E5/kGhKLDuT7+M83g4VWhgIvaAXtnhklDAggilPPa8ZJ1IFe31lNlr -k4DRk38nc6sEutdf3RL7QoH7FBusI7uXV03DC6dwN1kP4GE7bjJhcRb/7jYt7CQ9 -/E9Exz3c0yAp0yrTg0Fwh+qxfH9dKwN52S7SBwIDAQABAoIBAQCaCs26K07WY5Jt -3a2Cw3y2gPrIgTCqX6hJs7O5ByEhXZ8nBwsWANBUe4vrGaajQHdLj5OKfsIDrOvn -2NI1MqflqeAbu/kR32q3tq8/Rl+PPiwUsW3E6Pcf1orGMSNCXxeducF2iySySzh3 -nSIhCG5uwJDWI7a4+9KiieFgK1pt/Iv30q1SQS8IEntTfXYwANQrfKUVMmVF9aIK -6/WZE2yd5+q3wVVIJ6jsmTzoDCX6QQkkJICIYwCkglmVy5AeTckOVwcXL0jqw5Kf -5/soZJQwLEyBoQq7Kbpa26QHq+CJONetPP8Ssy8MJJXBT+u/bSseMb3Zsr5cr43e -DJOhwsThAoGBAPY6rPKl2NT/K7XfRCGm1sbWjUQyDShscwuWJ5+kD0yudnT/ZEJ1 -M3+KS/iOOAoHDdEDi9crRvMl0UfNa8MAcDKHflzxg2jg/QI+fTBjPP5GOX0lkZ9g -z6VePoVoQw2gpPFVNPPTxKfk27tEzbaffvOLGBEih0Kb7HTINkW8rIlzAoGBAM9y -1yr+jvfS1cGFtNU+Gotoihw2eMKtIqR03Yn3n0PK1nVCDKqwdUqCypz4+ml6cxRK -J8+Pfdh7D+ZJd4LEG6Y4QRDLuv5OA700tUoSHxMSNn3q9As4+T3MUyYxWKvTeu3U -f2NWP9ePU0lV8ttk7YlpVRaPQmc1qwooBA/z/8AdAoGAW9x0HWqmRICWTBnpjyxx -QGlW9rQ9mHEtUotIaRSJ6K/F3cxSGUEkX1a3FRnp6kPLcckC6NlqdNgNBd6rb2rA -cPl/uSkZP42Als+9YMoFPU/xrrDPbUhu72EDrj3Bllnyb168jKLa4VBOccUvggxr -Dm08I1hgYgdN5huzs7y6GeUCgYEAj+AZJSOJ6o1aXS6rfV3mMRve9bQ9yt8jcKXw -5HhOCEmMtaSKfnOF1Ziih34Sxsb7O2428DiX0mV/YHtBnPsAJidL0SdLWIapBzeg -KHArByIRkwE6IvJvwpGMdaex1PIGhx5i/3VZL9qiq/ElT05PhIb+UXgoWMabCp84 -OgxDK20CgYAeaFo8BdQ7FmVX2+EEejF+8xSge6WVLtkaon8bqcn6P0O8lLypoOhd -mJAYH8WU+UAy9pecUnDZj14LAGNVmYcse8HFX71MoshnvCTFEPVo4rZxIAGwMpeJ -5jgQ3slYLpqrGlcbLgUXBUgzEO684Wk/UV9DFPlHALVqCfXQ9dpJPg== ------END RSA PRIVATE KEY-----`) - -func TestJWTFetch_JSONResponse(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`{ - "access_token": "90d64460d14870c08c81352a05dedd3465940a7c", - "scope": "user", - "token_type": "bearer", - "expires_in": 3600 - }`)) - })) - defer ts.Close() - - conf := &Config{ - Email: "aaa@xxx.com", - PrivateKey: dummyPrivateKey, - TokenURL: ts.URL, - } - tok, err := conf.TokenSource(oauth2.NoContext).Token() - if err != nil { - t.Fatal(err) - } - if !tok.Valid() { - t.Errorf("Token invalid") - } - if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" { - t.Errorf("Unexpected access token, %#v", tok.AccessToken) - } - if tok.TokenType != "bearer" { - t.Errorf("Unexpected token type, %#v", tok.TokenType) - } - if tok.Expiry.IsZero() { - t.Errorf("Unexpected token expiry, %#v", tok.Expiry) - } - scope := tok.Extra("scope") - if scope != "user" { - t.Errorf("Unexpected value for scope: %v", scope) - } -} - -func TestJWTFetch_BadResponse(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`)) - })) - defer ts.Close() - - conf := &Config{ - Email: "aaa@xxx.com", - PrivateKey: dummyPrivateKey, - TokenURL: ts.URL, - } - tok, err := conf.TokenSource(oauth2.NoContext).Token() - if err != nil { - t.Fatal(err) - } - if tok == nil { - t.Fatalf("token is nil") - } - if tok.Valid() { - t.Errorf("token is valid. want invalid.") - } - if tok.AccessToken != "" { - t.Errorf("Unexpected non-empty access token %q.", tok.AccessToken) - } - if want := "bearer"; tok.TokenType != want { - t.Errorf("TokenType = %q; want %q", tok.TokenType, want) - } - scope := tok.Extra("scope") - if want := "user"; scope != want { - t.Errorf("token scope = %q; want %q", scope, want) - } -} - -func TestJWTFetch_BadResponseType(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`)) - })) - defer ts.Close() - conf := &Config{ - Email: "aaa@xxx.com", - PrivateKey: dummyPrivateKey, - TokenURL: ts.URL, - } - tok, err := conf.TokenSource(oauth2.NoContext).Token() - if err == nil { - t.Error("got a token; expected error") - if tok.AccessToken != "" { - t.Errorf("Unexpected access token, %#v.", tok.AccessToken) - } - } -} diff --git a/_third_party/golang.org/x/oauth2/oauth2.go b/_third_party/golang.org/x/oauth2/oauth2.go index 57931e4..042a113 100644 --- a/_third_party/golang.org/x/oauth2/oauth2.go +++ b/_third_party/golang.org/x/oauth2/oauth2.go @@ -25,14 +25,9 @@ import ( "github.com/mjibson/mog/_third_party/golang.org/x/net/context" ) -// Context can be an golang.org/x/net.Context, or an App Engine Context. -// If you don't care and aren't running on App Engine, you may use NoContext. -type Context interface{} - -// NoContext is the default context. If you're not running this code -// on App Engine or not using golang.org/x/net.Context to provide a custom -// HTTP client, you should use NoContext. -var NoContext Context = nil +// NoContext is the default context you should supply if not using +// your own context.Context (see https://golang.org/x/net/context). +var NoContext = context.TODO() // Config describes a typical 3-legged OAuth2 flow, with both the // client application information and the server's endpoint URLs. @@ -84,22 +79,28 @@ var ( // result in your application obtaining a refresh token the // first time your application exchanges an authorization // code for a user. - AccessTypeOnline AuthCodeOption = setParam{"access_type", "online"} - AccessTypeOffline AuthCodeOption = setParam{"access_type", "offline"} + AccessTypeOnline AuthCodeOption = SetAuthURLParam("access_type", "online") + AccessTypeOffline AuthCodeOption = SetAuthURLParam("access_type", "offline") // ApprovalForce forces the users to view the consent dialog // and confirm the permissions request at the URL returned // from AuthCodeURL, even if they've already done so. - ApprovalForce AuthCodeOption = setParam{"approval_prompt", "force"} + ApprovalForce AuthCodeOption = SetAuthURLParam("approval_prompt", "force") ) +// An AuthCodeOption is passed to Config.AuthCodeURL. +type AuthCodeOption interface { + setValue(url.Values) +} + type setParam struct{ k, v string } func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) } -// An AuthCodeOption is passed to Config.AuthCodeURL. -type AuthCodeOption interface { - setValue(url.Values) +// SetAuthURLParam builds an AuthCodeOption which passes key/value parameters +// to a provider's authorization endpoint. +func SetAuthURLParam(key, value string) AuthCodeOption { + return setParam{key, value} } // AuthCodeURL returns a URL to OAuth 2.0 provider's consent page @@ -143,9 +144,9 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string { // and when other authorization grant types are not available." // See https://tools.ietf.org/html/rfc6749#section-4.3 for more info. // -// The HTTP client to use is derived from the context. If nil, -// http.DefaultClient is used. See the Context type's documentation. -func (c *Config) PasswordCredentialsToken(ctx Context, username, password string) (*Token, error) { +// The HTTP client to use is derived from the context. +// If nil, http.DefaultClient is used. +func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) { return retrieveToken(ctx, c, url.Values{ "grant_type": {"password"}, "username": {username}, @@ -159,12 +160,12 @@ func (c *Config) PasswordCredentialsToken(ctx Context, username, password string // It is used after a resource provider redirects the user back // to the Redirect URI (the URL obtained from AuthCodeURL). // -// The HTTP client to use is derived from the context. If nil, -// http.DefaultClient is used. See the Context type's documentation. +// The HTTP client to use is derived from the context. +// If a client is not provided via the context, http.DefaultClient is used. // // The code will be in the *http.Request.FormValue("code"). Before // calling Exchange, be sure to validate FormValue("state"). -func (c *Config) Exchange(ctx Context, code string) (*Token, error) { +func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) { return retrieveToken(ctx, c, url.Values{ "grant_type": {"authorization_code"}, "code": {code}, @@ -177,7 +178,7 @@ func (c *Config) Exchange(ctx Context, code string) (*Token, error) { // given a Context value. If it returns an error, the search stops // with that error. If it returns (nil, nil), the search continues // down the list of registered funcs. -type contextClientFunc func(Context) (*http.Client, error) +type contextClientFunc func(context.Context) (*http.Client, error) var contextClientFuncs []contextClientFunc @@ -185,7 +186,7 @@ func registerContextClientFunc(fn contextClientFunc) { contextClientFuncs = append(contextClientFuncs, fn) } -func contextClient(ctx Context) (*http.Client, error) { +func contextClient(ctx context.Context) (*http.Client, error) { for _, fn := range contextClientFuncs { c, err := fn(ctx) if err != nil { @@ -195,15 +196,13 @@ func contextClient(ctx Context) (*http.Client, error) { return c, nil } } - if xc, ok := ctx.(context.Context); ok { - if hc, ok := xc.Value(HTTPClient).(*http.Client); ok { - return hc, nil - } + if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok { + return hc, nil } return http.DefaultClient, nil } -func contextTransport(ctx Context) http.RoundTripper { +func contextTransport(ctx context.Context) http.RoundTripper { hc, err := contextClient(ctx) if err != nil { // This is a rare error case (somebody using nil on App Engine), @@ -219,45 +218,57 @@ func contextTransport(ctx Context) http.RoundTripper { // The token will auto-refresh as necessary. The underlying // HTTP transport will be obtained using the provided context. // The returned client and its Transport should not be modified. -func (c *Config) Client(ctx Context, t *Token) *http.Client { +func (c *Config) Client(ctx context.Context, t *Token) *http.Client { return NewClient(ctx, c.TokenSource(ctx, t)) } // TokenSource returns a TokenSource that returns t until t expires, // automatically refreshing it as necessary using the provided context. -// See the the Context documentation. // // Most users will use Config.Client instead. -func (c *Config) TokenSource(ctx Context, t *Token) TokenSource { - nwn := &reuseTokenSource{t: t} - nwn.new = tokenRefresher{ - ctx: ctx, - conf: c, - oldToken: &nwn.t, - } - return nwn +func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource { + tkr := &tokenRefresher{ + ctx: ctx, + conf: c, + } + if t != nil { + tkr.refreshToken = t.RefreshToken + } + return &reuseTokenSource{ + t: t, + new: tkr, + } } // tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token" // HTTP requests to renew a token using a RefreshToken. type tokenRefresher struct { - ctx Context // used to get HTTP requests - conf *Config - oldToken **Token // pointer to old *Token w/ RefreshToken + ctx context.Context // used to get HTTP requests + conf *Config + refreshToken string } -func (tf tokenRefresher) Token() (*Token, error) { - t := *tf.oldToken - if t == nil { - return nil, errors.New("oauth2: attempted use of nil Token") - } - if t.RefreshToken == "" { +// WARNING: Token is not safe for concurrent access, as it +// updates the tokenRefresher's refreshToken field. +// Within this package, it is used by reuseTokenSource which +// synchronizes calls to this method with its own mutex. +func (tf *tokenRefresher) Token() (*Token, error) { + if tf.refreshToken == "" { return nil, errors.New("oauth2: token expired and refresh token is not set") } - return retrieveToken(tf.ctx, tf.conf, url.Values{ + + tk, err := retrieveToken(tf.ctx, tf.conf, url.Values{ "grant_type": {"refresh_token"}, - "refresh_token": {t.RefreshToken}, + "refresh_token": {tf.refreshToken}, }) + + if err != nil { + return nil, err + } + if tf.refreshToken != tk.RefreshToken { + tf.refreshToken = tk.RefreshToken + } + return tk, err } // reuseTokenSource is a TokenSource that holds a single token in memory @@ -288,7 +299,7 @@ func (s *reuseTokenSource) Token() (*Token, error) { return t, nil } -func retrieveToken(ctx Context, c *Config, v url.Values) (*Token, error) { +func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) { hc, err := contextClient(ctx) if err != nil { return nil, err @@ -303,7 +314,7 @@ func retrieveToken(ctx Context, c *Config, v url.Values) (*Token, error) { return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - if !bustedAuth && c.ClientSecret != "" { + if !bustedAuth { req.SetBasicAuth(c.ClientID, c.ClientSecret) } r, err := hc.Do(req) @@ -369,11 +380,11 @@ func retrieveToken(ctx Context, c *Config, v url.Values) (*Token, error) { // tokenJSON is the struct representing the HTTP response from OAuth2 // providers returning a token in JSON form. type tokenJSON struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - RefreshToken string `json:"refresh_token"` - ExpiresIn int32 `json:"expires_in"` - Expires int32 `json:"expires"` // broken Facebook spelling of expires_in + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + RefreshToken string `json:"refresh_token"` + ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number + Expires expirationTime `json:"expires"` // broken Facebook spelling of expires_in } func (e *tokenJSON) expiry() (t time.Time) { @@ -386,6 +397,22 @@ func (e *tokenJSON) expiry() (t time.Time) { return } +type expirationTime int32 + +func (e *expirationTime) UnmarshalJSON(b []byte) error { + var n json.Number + err := json.Unmarshal(b, &n) + if err != nil { + return err + } + i, err := n.Int64() + if err != nil { + return err + } + *e = expirationTime(i) + return nil +} + func condVal(v string) []string { if v == "" { return nil @@ -393,6 +420,25 @@ func condVal(v string) []string { return []string{v} } +var brokenAuthHeaderProviders = []string{ + "https://accounts.google.com/", + "https://www.googleapis.com/", + "https://github.com/", + "https://api.instagram.com/", + "https://www.douban.com/", + "https://api.dropbox.com/", + "https://api.soundcloud.com/", + "https://www.linkedin.com/", + "https://api.twitch.tv/", + "https://oauth.vk.com/", + "https://api.odnoklassniki.ru/", + "https://connect.stripe.com/", + "https://api.pushbullet.com/", + "https://oauth.sandbox.trainingpeaks.com/", + "https://oauth.trainingpeaks.com/", + "https://www.strava.com/oauth/", +} + // providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL // implements the OAuth2 spec correctly // See https://code.google.com/p/goauth2/issues/detail?id=31 for background. @@ -400,17 +446,13 @@ func condVal(v string) []string { // - Reddit only accepts client secret in the Authorization header // - Dropbox accepts either it in URL param or Auth header, but not both. // - Google only accepts URL param (not spec compliant?), not Auth header +// - Stripe only accepts client secret in Auth header with Bearer method, not Basic func providerAuthHeaderWorks(tokenURL string) bool { - if strings.HasPrefix(tokenURL, "https://accounts.google.com/") || - strings.HasPrefix(tokenURL, "https://www.googleapis.com/") || - strings.HasPrefix(tokenURL, "https://github.com/") || - strings.HasPrefix(tokenURL, "https://api.instagram.com/") || - strings.HasPrefix(tokenURL, "https://www.douban.com/") || - strings.HasPrefix(tokenURL, "https://api.dropbox.com/") || - strings.HasPrefix(tokenURL, "https://api.soundcloud.com/") || - strings.HasPrefix(tokenURL, "https://www.linkedin.com/") { - // Some sites fail to implement the OAuth2 spec fully. - return false + for _, s := range brokenAuthHeaderProviders { + if strings.HasPrefix(tokenURL, s) { + // Some sites fail to implement the OAuth2 spec fully. + return false + } } // Assume the provider implements the spec properly @@ -435,7 +477,7 @@ type contextKey struct{} // As a special case, if src is nil, a non-OAuth2 client is returned // using the provided context. This exists to support related OAuth2 // packages. -func NewClient(ctx Context, src TokenSource) *http.Client { +func NewClient(ctx context.Context, src TokenSource) *http.Client { if src == nil { c, err := contextClient(ctx) if err != nil { diff --git a/_third_party/golang.org/x/oauth2/oauth2_test.go b/_third_party/golang.org/x/oauth2/oauth2_test.go deleted file mode 100644 index 65786ba..0000000 --- a/_third_party/golang.org/x/oauth2/oauth2_test.go +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright 2014 The oauth2 Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package oauth2 - -import ( - "errors" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/mjibson/mog/_third_party/golang.org/x/net/context" -) - -type mockTransport struct { - rt func(req *http.Request) (resp *http.Response, err error) -} - -func (t *mockTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { - return t.rt(req) -} - -type mockCache struct { - token *Token - readErr error -} - -func (c *mockCache) ReadToken() (*Token, error) { - return c.token, c.readErr -} - -func (c *mockCache) WriteToken(*Token) { - // do nothing -} - -func newConf(url string) *Config { - return &Config{ - ClientID: "CLIENT_ID", - ClientSecret: "CLIENT_SECRET", - RedirectURL: "REDIRECT_URL", - Scopes: []string{"scope1", "scope2"}, - Endpoint: Endpoint{ - AuthURL: url + "/auth", - TokenURL: url + "/token", - }, - } -} - -func TestAuthCodeURL(t *testing.T) { - conf := newConf("server") - url := conf.AuthCodeURL("foo", AccessTypeOffline, ApprovalForce) - if url != "server/auth?access_type=offline&approval_prompt=force&client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=foo" { - t.Errorf("Auth code URL doesn't match the expected, found: %v", url) - } -} - -func TestAuthCodeURL_Optional(t *testing.T) { - conf := &Config{ - ClientID: "CLIENT_ID", - Endpoint: Endpoint{ - AuthURL: "/auth-url", - TokenURL: "/token-url", - }, - } - url := conf.AuthCodeURL("") - if url != "/auth-url?client_id=CLIENT_ID&response_type=code" { - t.Fatalf("Auth code URL doesn't match the expected, found: %v", url) - } -} - -func TestExchangeRequest(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.String() != "/token" { - t.Errorf("Unexpected exchange request URL, %v is found.", r.URL) - } - headerAuth := r.Header.Get("Authorization") - if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" { - t.Errorf("Unexpected authorization header, %v is found.", headerAuth) - } - headerContentType := r.Header.Get("Content-Type") - if headerContentType != "application/x-www-form-urlencoded" { - t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType) - } - body, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Errorf("Failed reading request body: %s.", err) - } - if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" { - t.Errorf("Unexpected exchange payload, %v is found.", string(body)) - } - w.Header().Set("Content-Type", "application/x-www-form-urlencoded") - w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer")) - })) - defer ts.Close() - conf := newConf(ts.URL) - tok, err := conf.Exchange(NoContext, "exchange-code") - if err != nil { - t.Error(err) - } - if !tok.Valid() { - t.Fatalf("Token invalid. Got: %#v", tok) - } - if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" { - t.Errorf("Unexpected access token, %#v.", tok.AccessToken) - } - if tok.TokenType != "bearer" { - t.Errorf("Unexpected token type, %#v.", tok.TokenType) - } - scope := tok.Extra("scope") - if scope != "user" { - t.Errorf("Unexpected value for scope: %v", scope) - } -} - -func TestExchangeRequest_JSONResponse(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.String() != "/token" { - t.Errorf("Unexpected exchange request URL, %v is found.", r.URL) - } - headerAuth := r.Header.Get("Authorization") - if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" { - t.Errorf("Unexpected authorization header, %v is found.", headerAuth) - } - headerContentType := r.Header.Get("Content-Type") - if headerContentType != "application/x-www-form-urlencoded" { - t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType) - } - body, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Errorf("Failed reading request body: %s.", err) - } - if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" { - t.Errorf("Unexpected exchange payload, %v is found.", string(body)) - } - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`{"access_token": "90d64460d14870c08c81352a05dedd3465940a7c", "scope": "user", "token_type": "bearer", "expires_in": 86400}`)) - })) - defer ts.Close() - conf := newConf(ts.URL) - tok, err := conf.Exchange(NoContext, "exchange-code") - if err != nil { - t.Error(err) - } - if !tok.Valid() { - t.Fatalf("Token invalid. Got: %#v", tok) - } - if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" { - t.Errorf("Unexpected access token, %#v.", tok.AccessToken) - } - if tok.TokenType != "bearer" { - t.Errorf("Unexpected token type, %#v.", tok.TokenType) - } - scope := tok.Extra("scope") - if scope != "user" { - t.Errorf("Unexpected value for scope: %v", scope) - } -} - -func TestExchangeRequest_BadResponse(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`)) - })) - defer ts.Close() - conf := newConf(ts.URL) - tok, err := conf.Exchange(NoContext, "code") - if err != nil { - t.Fatal(err) - } - if tok.AccessToken != "" { - t.Errorf("Unexpected access token, %#v.", tok.AccessToken) - } -} - -func TestExchangeRequest_BadResponseType(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`)) - })) - defer ts.Close() - conf := newConf(ts.URL) - _, err := conf.Exchange(NoContext, "exchange-code") - if err == nil { - t.Error("expected error from invalid access_token type") - } -} - -func TestExchangeRequest_NonBasicAuth(t *testing.T) { - tr := &mockTransport{ - rt: func(r *http.Request) (w *http.Response, err error) { - headerAuth := r.Header.Get("Authorization") - if headerAuth != "" { - t.Errorf("Unexpected authorization header, %v is found.", headerAuth) - } - return nil, errors.New("no response") - }, - } - c := &http.Client{Transport: tr} - conf := &Config{ - ClientID: "CLIENT_ID", - Endpoint: Endpoint{ - AuthURL: "https://accounts.google.com/auth", - TokenURL: "https://accounts.google.com/token", - }, - } - - ctx := context.WithValue(context.Background(), HTTPClient, c) - conf.Exchange(ctx, "code") -} - -func TestPasswordCredentialsTokenRequest(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - expected := "/token" - if r.URL.String() != expected { - t.Errorf("URL = %q; want %q", r.URL, expected) - } - headerAuth := r.Header.Get("Authorization") - expected = "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" - if headerAuth != expected { - t.Errorf("Authorization header = %q; want %q", headerAuth, expected) - } - headerContentType := r.Header.Get("Content-Type") - expected = "application/x-www-form-urlencoded" - if headerContentType != expected { - t.Errorf("Content-Type header = %q; want %q", headerContentType, expected) - } - body, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Errorf("Failed reading request body: %s.", err) - } - expected = "client_id=CLIENT_ID&grant_type=password&password=password1&scope=scope1+scope2&username=user1" - if string(body) != expected { - t.Errorf("res.Body = %q; want %q", string(body), expected) - } - w.Header().Set("Content-Type", "application/x-www-form-urlencoded") - w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer")) - })) - defer ts.Close() - conf := newConf(ts.URL) - tok, err := conf.PasswordCredentialsToken(NoContext, "user1", "password1") - if err != nil { - t.Error(err) - } - if !tok.Valid() { - t.Fatalf("Token invalid. Got: %#v", tok) - } - expected := "90d64460d14870c08c81352a05dedd3465940a7c" - if tok.AccessToken != expected { - t.Errorf("AccessToken = %q; want %q", tok.AccessToken, expected) - } - expected = "bearer" - if tok.TokenType != expected { - t.Errorf("TokenType = %q; want %q", tok.TokenType, expected) - } -} - -func TestTokenRefreshRequest(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.String() == "/somethingelse" { - return - } - if r.URL.String() != "/token" { - t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL) - } - headerContentType := r.Header.Get("Content-Type") - if headerContentType != "application/x-www-form-urlencoded" { - t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType) - } - body, _ := ioutil.ReadAll(r.Body) - if string(body) != "client_id=CLIENT_ID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" { - t.Errorf("Unexpected refresh token payload, %v is found.", string(body)) - } - })) - defer ts.Close() - conf := newConf(ts.URL) - c := conf.Client(NoContext, &Token{RefreshToken: "REFRESH_TOKEN"}) - c.Get(ts.URL + "/somethingelse") -} - -func TestFetchWithNoRefreshToken(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.String() == "/somethingelse" { - return - } - if r.URL.String() != "/token" { - t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL) - } - headerContentType := r.Header.Get("Content-Type") - if headerContentType != "application/x-www-form-urlencoded" { - t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType) - } - body, _ := ioutil.ReadAll(r.Body) - if string(body) != "client_id=CLIENT_ID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" { - t.Errorf("Unexpected refresh token payload, %v is found.", string(body)) - } - })) - defer ts.Close() - conf := newConf(ts.URL) - c := conf.Client(NoContext, nil) - _, err := c.Get(ts.URL + "/somethingelse") - if err == nil { - t.Errorf("Fetch should return an error if no refresh token is set") - } -} diff --git a/_third_party/golang.org/x/oauth2/token_test.go b/_third_party/golang.org/x/oauth2/token_test.go deleted file mode 100644 index 739eeb2..0000000 --- a/_third_party/golang.org/x/oauth2/token_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2014 The oauth2 Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package oauth2 - -import ( - "testing" - "time" -) - -func TestTokenExtra(t *testing.T) { - type testCase struct { - key string - val interface{} - want interface{} - } - const key = "extra-key" - cases := []testCase{ - {key: key, val: "abc", want: "abc"}, - {key: key, val: 123, want: 123}, - {key: key, val: "", want: ""}, - {key: "other-key", val: "def", want: nil}, - } - for _, tc := range cases { - extra := make(map[string]interface{}) - extra[tc.key] = tc.val - tok := &Token{raw: extra} - if got, want := tok.Extra(key), tc.want; got != want { - t.Errorf("Extra(%q) = %q; want %q", key, got, want) - } - } -} - -func TestTokenExpiry(t *testing.T) { - now := time.Now() - cases := []struct { - name string - tok *Token - want bool - }{ - {name: "12 seconds", tok: &Token{Expiry: now.Add(12 * time.Second)}, want: false}, - {name: "10 seconds", tok: &Token{Expiry: now.Add(expiryDelta)}, want: true}, - } - for _, tc := range cases { - if got, want := tc.tok.expired(), tc.want; got != want { - t.Errorf("expired (%q) = %v; want %v", tc.name, got, want) - } - } -} diff --git a/_third_party/golang.org/x/oauth2/transport_test.go b/_third_party/golang.org/x/oauth2/transport_test.go deleted file mode 100644 index efb8232..0000000 --- a/_third_party/golang.org/x/oauth2/transport_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package oauth2 - -import ( - "net/http" - "net/http/httptest" - "testing" - "time" -) - -type tokenSource struct{ token *Token } - -func (t *tokenSource) Token() (*Token, error) { - return t.token, nil -} - -func TestTransportTokenSource(t *testing.T) { - ts := &tokenSource{ - token: &Token{ - AccessToken: "abc", - }, - } - tr := &Transport{ - Source: ts, - } - server := newMockServer(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Authorization") != "Bearer abc" { - t.Errorf("Transport doesn't set the Authorization header from the fetched token") - } - }) - defer server.Close() - client := http.Client{Transport: tr} - client.Get(server.URL) -} - -func TestTokenValidNoAccessToken(t *testing.T) { - token := &Token{} - if token.Valid() { - t.Errorf("Token should not be valid with no access token") - } -} - -func TestExpiredWithExpiry(t *testing.T) { - token := &Token{ - Expiry: time.Now().Add(-5 * time.Hour), - } - if token.Valid() { - t.Errorf("Token should not be valid if it expired in the past") - } -} - -func newMockServer(handler func(w http.ResponseWriter, r *http.Request)) *httptest.Server { - return httptest.NewServer(http.HandlerFunc(handler)) -} diff --git a/_third_party/google.golang.org/cloud/internal/cloud.go b/_third_party/google.golang.org/cloud/internal/cloud.go index c39b61d..8a5ea41 100644 --- a/_third_party/google.golang.org/cloud/internal/cloud.go +++ b/_third_party/google.golang.org/cloud/internal/cloud.go @@ -90,7 +90,7 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { if ua == "" { ua = userAgent } else { - ua = fmt.Sprintf("%s;%s", ua, userAgent) + ua = fmt.Sprintf("%s %s", ua, userAgent) } req.Header.Set("User-Agent", ua) return t.Base.RoundTrip(req) diff --git a/_third_party/gopkg.in/fsnotify.v1/AUTHORS b/_third_party/gopkg.in/fsnotify.v1/AUTHORS deleted file mode 100644 index 440cda4..0000000 --- a/_third_party/gopkg.in/fsnotify.v1/AUTHORS +++ /dev/null @@ -1,33 +0,0 @@ -# Names should be added to this file as -# Name or Organization -# The email address is not required for organizations. - -# You can update this list using the following command: -# -# $ git shortlog -se | awk '{print $2 " " $3 " " $4}' - -# Please keep the list sorted. - -Adrien Bustany -Caleb Spare -Case Nelson -Chris Howey -Christoffer Buchholz -Dave Cheney -Francisco Souza -Hari haran -John C Barstow -Kelvin Fo -Matt Layher -Nathan Youngman -Paul Hammond -Pursuit92 -Rob Figueiredo -Soge Zhang -Tilak Sharma -Travis Cline -Tudor Golubenco -Yukang -bronze1man -debrando -henrikedwards diff --git a/_third_party/gopkg.in/fsnotify.v1/CHANGELOG.md b/_third_party/gopkg.in/fsnotify.v1/CHANGELOG.md deleted file mode 100644 index fe4c3ba..0000000 --- a/_third_party/gopkg.in/fsnotify.v1/CHANGELOG.md +++ /dev/null @@ -1,257 +0,0 @@ -# Changelog - -## v1.1.1 / 2015-02-05 - -* inotify: Retry read on EINTR [#61](https://github.com/go-fsnotify/fsnotify/issues/61) (thanks @PieterD) - -## v1.1.0 / 2014-12-12 - -* kqueue: rework internals [#43](https://github.com/go-fsnotify/fsnotify/pull/43) - * add low-level functions - * only need to store flags on directories - * less mutexes [#13](https://github.com/go-fsnotify/fsnotify/issues/13) - * done can be an unbuffered channel - * remove calls to os.NewSyscallError -* More efficient string concatenation for Event.String() [#52](https://github.com/go-fsnotify/fsnotify/pull/52) (thanks @mdlayher) -* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/go-fsnotify/fsnotify/issues/48) -* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/51) - -## v1.0.4 / 2014-09-07 - -* kqueue: add dragonfly to the build tags. -* Rename source code files, rearrange code so exported APIs are at the top. -* Add done channel to example code. [#37](https://github.com/go-fsnotify/fsnotify/pull/37) (thanks @chenyukang) - -## v1.0.3 / 2014-08-19 - -* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/go-fsnotify/fsnotify/issues/36) - -## v1.0.2 / 2014-08-17 - -* [Fix] Missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso) -* [Fix] Make ./path and path equivalent. (thanks @zhsso) - -## v1.0.0 / 2014-08-15 - -* [API] Remove AddWatch on Windows, use Add. -* Improve documentation for exported identifiers. [#30](https://github.com/go-fsnotify/fsnotify/issues/30) -* Minor updates based on feedback from golint. - -## dev / 2014-07-09 - -* Moved to [github.com/go-fsnotify/fsnotify](https://github.com/go-fsnotify/fsnotify). -* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno) - -## dev / 2014-07-04 - -* kqueue: fix incorrect mutex used in Close() -* Update example to demonstrate usage of Op. - -## dev / 2014-06-28 - -* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/go-fsnotify/fsnotify/issues/4) -* Fix for String() method on Event (thanks Alex Brainman) -* Don't build on Plan 9 or Solaris (thanks @4ad) - -## dev / 2014-06-21 - -* Events channel of type Event rather than *Event. -* [internal] use syscall constants directly for inotify and kqueue. -* [internal] kqueue: rename events to kevents and fileEvent to event. - -## dev / 2014-06-19 - -* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally). -* [internal] remove cookie from Event struct (unused). -* [internal] Event struct has the same definition across every OS. -* [internal] remove internal watch and removeWatch methods. - -## dev / 2014-06-12 - -* [API] Renamed Watch() to Add() and RemoveWatch() to Remove(). -* [API] Pluralized channel names: Events and Errors. -* [API] Renamed FileEvent struct to Event. -* [API] Op constants replace methods like IsCreate(). - -## dev / 2014-06-12 - -* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) - -## dev / 2014-05-23 - -* [API] Remove current implementation of WatchFlags. - * current implementation doesn't take advantage of OS for efficiency - * provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes - * no tests for the current implementation - * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195) - -## v0.9.3 / 2014-12-31 - -* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/51) - -## v0.9.2 / 2014-08-17 - -* [Backport] Fix missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso) - -## v0.9.1 / 2014-06-12 - -* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) - -## v0.9.0 / 2014-01-17 - -* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany) -* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare) -* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library. - -## v0.8.12 / 2013-11-13 - -* [API] Remove FD_SET and friends from Linux adapter - -## v0.8.11 / 2013-11-02 - -* [Doc] Add Changelog [#72][] (thanks @nathany) -* [Doc] Spotlight and double modify events on OS X [#62][] (reported by @paulhammond) - -## v0.8.10 / 2013-10-19 - -* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott) -* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer) -* [Doc] specify OS-specific limits in README (thanks @debrando) - -## v0.8.9 / 2013-09-08 - -* [Doc] Contributing (thanks @nathany) -* [Doc] update package path in example code [#63][] (thanks @paulhammond) -* [Doc] GoCI badge in README (Linux only) [#60][] -* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany) - -## v0.8.8 / 2013-06-17 - -* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie) - -## v0.8.7 / 2013-06-03 - -* [API] Make syscall flags internal -* [Fix] inotify: ignore event changes -* [Fix] race in symlink test [#45][] (reported by @srid) -* [Fix] tests on Windows -* lower case error messages - -## v0.8.6 / 2013-05-23 - -* kqueue: Use EVT_ONLY flag on Darwin -* [Doc] Update README with full example - -## v0.8.5 / 2013-05-09 - -* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg) - -## v0.8.4 / 2013-04-07 - -* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz) - -## v0.8.3 / 2013-03-13 - -* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin) -* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin) - -## v0.8.2 / 2013-02-07 - -* [Doc] add Authors -* [Fix] fix data races for map access [#29][] (thanks @fsouza) - -## v0.8.1 / 2013-01-09 - -* [Fix] Windows path separators -* [Doc] BSD License - -## v0.8.0 / 2012-11-09 - -* kqueue: directory watching improvements (thanks @vmirage) -* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto) -* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr) - -## v0.7.4 / 2012-10-09 - -* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji) -* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig) -* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig) -* [Fix] kqueue: modify after recreation of file - -## v0.7.3 / 2012-09-27 - -* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage) -* [Fix] kqueue: no longer get duplicate CREATE events - -## v0.7.2 / 2012-09-01 - -* kqueue: events for created directories - -## v0.7.1 / 2012-07-14 - -* [Fix] for renaming files - -## v0.7.0 / 2012-07-02 - -* [Feature] FSNotify flags -* [Fix] inotify: Added file name back to event path - -## v0.6.0 / 2012-06-06 - -* kqueue: watch files after directory created (thanks @tmc) - -## v0.5.1 / 2012-05-22 - -* [Fix] inotify: remove all watches before Close() - -## v0.5.0 / 2012-05-03 - -* [API] kqueue: return errors during watch instead of sending over channel -* kqueue: match symlink behavior on Linux -* inotify: add `DELETE_SELF` (requested by @taralx) -* [Fix] kqueue: handle EINTR (reported by @robfig) -* [Doc] Godoc example [#1][] (thanks @davecheney) - -## v0.4.0 / 2012-03-30 - -* Go 1 released: build with go tool -* [Feature] Windows support using winfsnotify -* Windows does not have attribute change notifications -* Roll attribute notifications into IsModify - -## v0.3.0 / 2012-02-19 - -* kqueue: add files when watch directory - -## v0.2.0 / 2011-12-30 - -* update to latest Go weekly code - -## v0.1.0 / 2011-10-19 - -* kqueue: add watch on file creation to match inotify -* kqueue: create file event -* inotify: ignore `IN_IGNORED` events -* event String() -* linux: common FileEvent functions -* initial commit - -[#79]: https://github.com/howeyc/fsnotify/pull/79 -[#77]: https://github.com/howeyc/fsnotify/pull/77 -[#72]: https://github.com/howeyc/fsnotify/issues/72 -[#71]: https://github.com/howeyc/fsnotify/issues/71 -[#70]: https://github.com/howeyc/fsnotify/issues/70 -[#63]: https://github.com/howeyc/fsnotify/issues/63 -[#62]: https://github.com/howeyc/fsnotify/issues/62 -[#60]: https://github.com/howeyc/fsnotify/issues/60 -[#59]: https://github.com/howeyc/fsnotify/issues/59 -[#49]: https://github.com/howeyc/fsnotify/issues/49 -[#45]: https://github.com/howeyc/fsnotify/issues/45 -[#40]: https://github.com/howeyc/fsnotify/issues/40 -[#36]: https://github.com/howeyc/fsnotify/issues/36 -[#33]: https://github.com/howeyc/fsnotify/issues/33 -[#29]: https://github.com/howeyc/fsnotify/issues/29 -[#25]: https://github.com/howeyc/fsnotify/issues/25 -[#24]: https://github.com/howeyc/fsnotify/issues/24 -[#21]: https://github.com/howeyc/fsnotify/issues/21 - diff --git a/_third_party/gopkg.in/fsnotify.v1/CONTRIBUTING.md b/_third_party/gopkg.in/fsnotify.v1/CONTRIBUTING.md deleted file mode 100644 index 0f377f3..0000000 --- a/_third_party/gopkg.in/fsnotify.v1/CONTRIBUTING.md +++ /dev/null @@ -1,77 +0,0 @@ -# Contributing - -## Issues - -* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues). -* Please indicate the platform you are using fsnotify on. -* A code example to reproduce the problem is appreciated. - -## Pull Requests - -### Contributor License Agreement - -fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/go-fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). - -Please indicate that you have signed the CLA in your pull request. - -### How fsnotify is Developed - -* Development is done on feature branches. -* Tests are run on BSD, Linux, OS X and Windows. -* Pull requests are reviewed and [applied to master][am] using [hub][]. - * Maintainers may modify or squash commits rather than asking contributors to. -* To issue a new release, the maintainers will: - * Update the CHANGELOG - * Tag a version, which will become available through gopkg.in. - -### How to Fork - -For smooth sailing, always use the original import path. Installing with `go get` makes this easy. - -1. Install from GitHub (`go get -u github.com/go-fsnotify/fsnotify`) -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Ensure everything works and the tests pass (see below) -4. Commit your changes (`git commit -am 'Add some feature'`) - -Contribute upstream: - -1. Fork fsnotify on GitHub -2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`) -3. Push to the branch (`git push fork my-new-feature`) -4. Create a new Pull Request on GitHub - -This workflow is [thoroughly explained by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/). - -### Testing - -fsnotify uses build tags to compile different code on Linux, BSD, OS X, and Windows. - -Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on. - -To aid in cross-platform testing there is a Vagrantfile for Linux and BSD. - -* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) -* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder. -* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password) -* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd go-fsnotify/fsnotify; go test'`. -* When you're done, you will want to halt or destroy the Vagrant boxes. - -Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory. - -Right now there is no equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads). - -### Maintainers - -Help maintaining fsnotify is welcome. To be a maintainer: - -* Submit a pull request and sign the CLA as above. -* You must be able to run the test suite on Mac, Windows, Linux and BSD. - -To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][]. - -All code changes should be internal pull requests. - -Releases are tagged using [Semantic Versioning](http://semver.org/). - -[hub]: https://github.com/github/hub -[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs diff --git a/_third_party/gopkg.in/fsnotify.v1/LICENSE b/_third_party/gopkg.in/fsnotify.v1/LICENSE deleted file mode 100644 index f21e540..0000000 --- a/_third_party/gopkg.in/fsnotify.v1/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2012 The Go Authors. All rights reserved. -Copyright (c) 2012 fsnotify Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/_third_party/gopkg.in/fsnotify.v1/NotUsed.xcworkspace b/_third_party/gopkg.in/fsnotify.v1/NotUsed.xcworkspace deleted file mode 100644 index e69de29..0000000 diff --git a/_third_party/gopkg.in/fsnotify.v1/README.md b/_third_party/gopkg.in/fsnotify.v1/README.md deleted file mode 100644 index 7a0b247..0000000 --- a/_third_party/gopkg.in/fsnotify.v1/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# File system notifications for Go - -[![Coverage](http://gocover.io/_badge/github.com/go-fsnotify/fsnotify)](http://gocover.io/github.com/go-fsnotify/fsnotify) [![GoDoc](https://godoc.org/gopkg.in/fsnotify.v1?status.svg)](https://godoc.org/gopkg.in/fsnotify.v1) - -Go 1.3+ required. - -Cross platform: Windows, Linux, BSD and OS X. - -|Adapter |OS |Status | -|----------|----------|----------| -|inotify |Linux, Android\*|Supported [![Build Status](https://travis-ci.org/go-fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/go-fsnotify/fsnotify)| -|kqueue |BSD, OS X, iOS\*|Supported [![Circle CI](https://circleci.com/gh/go-fsnotify/fsnotify.svg?style=svg)](https://circleci.com/gh/go-fsnotify/fsnotify)| -|ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)| -|FSEvents |OS X |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)| -|FEN |Solaris 11 |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)| -|fanotify |Linux 2.6.37+ | | -|USN Journals |Windows |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/53)| -|Polling |*All* |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/9)| - -\* Android and iOS are untested. - -Please see [the documentation](https://godoc.org/gopkg.in/fsnotify.v1) for usage. Consult the [Wiki](https://github.com/go-fsnotify/fsnotify/wiki) for the FAQ and further information. - -## API stability - -Two major versions of fsnotify exist. - -**[fsnotify.v0](https://gopkg.in/fsnotify.v0)** is API-compatible with [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify). Bugfixes *may* be backported, but I recommend upgrading to v1. - -```go -import "gopkg.in/fsnotify.v0" -``` - -\* Refer to the package as fsnotify (without the .v0 suffix). - -**[fsnotify.v1](https://gopkg.in/fsnotify.v1)** provides [a new API](https://godoc.org/gopkg.in/fsnotify.v1) based on [this design document](http://goo.gl/MrYxyA). You can import v1 with: - -```go -import "gopkg.in/fsnotify.v1" -``` - -Further API changes are [planned](https://github.com/go-fsnotify/fsnotify/milestones), but a new major revision will be tagged, so you can depend on the v1 API. - -**Master** may have unreleased changes. Use it to test the very latest code or when [contributing][], but don't expect it to remain API-compatible: - -```go -import "github.com/go-fsnotify/fsnotify" -``` - -## Contributing - -Please refer to [CONTRIBUTING][] before opening an issue or pull request. - -## Example - -See [example_test.go](https://github.com/go-fsnotify/fsnotify/blob/master/example_test.go). - - -[contributing]: https://github.com/go-fsnotify/fsnotify/blob/master/CONTRIBUTING.md diff --git a/_third_party/gopkg.in/fsnotify.v1/circle.yml b/_third_party/gopkg.in/fsnotify.v1/circle.yml deleted file mode 100644 index 204217f..0000000 --- a/_third_party/gopkg.in/fsnotify.v1/circle.yml +++ /dev/null @@ -1,26 +0,0 @@ -## OS X build (CircleCI iOS beta) - -# Pretend like it's an Xcode project, at least to get it running. -machine: - environment: - XCODE_WORKSPACE: NotUsed.xcworkspace - XCODE_SCHEME: NotUsed - # This is where the go project is actually checked out to: - CIRCLE_BUILD_DIR: $HOME/.go_project/src/github.com/go-fsnotify/fsnotify - -dependencies: - pre: - - brew upgrade go - -test: - override: - - go test ./... - -# Idealized future config, eventually with cross-platform build matrix :-) - -# machine: -# go: -# version: 1.4 -# os: -# - osx -# - linux diff --git a/_third_party/gopkg.in/fsnotify.v1/example_test.go b/_third_party/gopkg.in/fsnotify.v1/example_test.go deleted file mode 100644 index aa6fd7e..0000000 --- a/_third_party/gopkg.in/fsnotify.v1/example_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !plan9,!solaris - -package fsnotify_test - -import ( - "log" - - "github.com/mjibson/mog/_third_party/github.com/go-fsnotify/fsnotify" -) - -func ExampleNewWatcher() { - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Fatal(err) - } - defer watcher.Close() - - done := make(chan bool) - go func() { - for { - select { - case event := <-watcher.Events: - log.Println("event:", event) - if event.Op&fsnotify.Write == fsnotify.Write { - log.Println("modified file:", event.Name) - } - case err := <-watcher.Errors: - log.Println("error:", err) - } - } - }() - - err = watcher.Add("/tmp/foo") - if err != nil { - log.Fatal(err) - } - <-done -} diff --git a/_third_party/gopkg.in/fsnotify.v1/inotify.go b/_third_party/gopkg.in/fsnotify.v1/inotify.go index 1a693f3..d7759ec 100644 --- a/_third_party/gopkg.in/fsnotify.v1/inotify.go +++ b/_third_party/gopkg.in/fsnotify.v1/inotify.go @@ -9,6 +9,7 @@ package fsnotify import ( "errors" "fmt" + "io" "os" "path/filepath" "strings" @@ -21,47 +22,66 @@ import ( type Watcher struct { Events chan Event Errors chan error - mu sync.Mutex // Map access - fd int // File descriptor (as returned by the inotify_init() syscall) + mu sync.Mutex // Map access + fd int + poller *fdPoller watches map[string]*watch // Map of inotify watches (key: path) paths map[int]string // Map of watched paths (key: watch descriptor) - done chan bool // Channel for sending a "quit message" to the reader goroutine - isClosed bool // Set to true when Close() is first called + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + doneResp chan struct{} // Channel to respond to Close } // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. func NewWatcher() (*Watcher, error) { + // Create inotify fd fd, errno := syscall.InotifyInit() if fd == -1 { - return nil, os.NewSyscallError("inotify_init", errno) + return nil, errno + } + // Create epoll + poller, err := newFdPoller(fd) + if err != nil { + syscall.Close(fd) + return nil, err } w := &Watcher{ - fd: fd, - watches: make(map[string]*watch), - paths: make(map[int]string), - Events: make(chan Event), - Errors: make(chan error), - done: make(chan bool, 1), + fd: fd, + poller: poller, + watches: make(map[string]*watch), + paths: make(map[int]string), + Events: make(chan Event), + Errors: make(chan error), + done: make(chan struct{}), + doneResp: make(chan struct{}), } go w.readEvents() return w, nil } +func (w *Watcher) isClosed() bool { + select { + case <-w.done: + return true + default: + return false + } +} + // Close removes all watches and closes the events channel. func (w *Watcher) Close() error { - if w.isClosed { + if w.isClosed() { return nil } - w.isClosed = true - // Remove all watches - for name := range w.watches { - w.Remove(name) - } + // Send 'close' signal to goroutine, and set the Watcher to closed. + close(w.done) - // Send "quit" message to the reader goroutine - w.done <- true + // Wake up goroutine + w.poller.wake() + + // Wait for goroutine to close + <-w.doneResp return nil } @@ -69,7 +89,7 @@ func (w *Watcher) Close() error { // Add starts watching the named file or directory (non-recursively). func (w *Watcher) Add(name string) error { name = filepath.Clean(name) - if w.isClosed { + if w.isClosed() { return errors.New("inotify instance already closed") } @@ -88,7 +108,7 @@ func (w *Watcher) Add(name string) error { } wd, errno := syscall.InotifyAddWatch(w.fd, name, flags) if wd == -1 { - return os.NewSyscallError("inotify_add_watch", errno) + return errno } w.mu.Lock() @@ -99,20 +119,33 @@ func (w *Watcher) Add(name string) error { return nil } -// Remove stops watching the the named file or directory (non-recursively). +// Remove stops watching the named file or directory (non-recursively). func (w *Watcher) Remove(name string) error { name = filepath.Clean(name) + + // Fetch the watch. w.mu.Lock() defer w.mu.Unlock() watch, ok := w.watches[name] + + // Remove it from inotify. if !ok { return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) } + // inotify_rm_watch will return EINVAL if the file has been deleted; + // the inotify will already have been removed. + // That means we can safely delete it from our watches, whatever inotify_rm_watch does. + delete(w.watches, name) success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) if success == -1 { - return os.NewSyscallError("inotify_rm_watch", errno) + // TODO: Perhaps it's not helpful to return an error here in every case. + // the only two possible errors are: + // EBADF, which happens when w.fd is not a valid file descriptor of any kind. + // EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor. + // Watch descriptors are invalidated when they are removed explicitly or implicitly; + // explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted. + return errno } - delete(w.watches, name) return nil } @@ -128,17 +161,33 @@ func (w *Watcher) readEvents() { buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events n int // Number of bytes read with read() errno error // Syscall errno + ok bool // For poller.wait ) + defer close(w.doneResp) + defer close(w.Errors) + defer close(w.Events) + defer syscall.Close(w.fd) + defer w.poller.close() + for { - // See if there is a message on the "done" channel - select { - case <-w.done: - syscall.Close(w.fd) - close(w.Events) - close(w.Errors) + // See if we have been closed. + if w.isClosed() { return - default: + } + + ok, errno = w.poller.wait() + if errno != nil { + select { + case w.Errors <- errno: + case <-w.done: + return + } + continue + } + + if !ok { + continue } n, errno = syscall.Read(w.fd, buf[:]) @@ -149,20 +198,28 @@ func (w *Watcher) readEvents() { continue } - // If EOF is received - if n == 0 { - syscall.Close(w.fd) - close(w.Events) - close(w.Errors) + // syscall.Read might have been woken up by Close. If so, we're done. + if w.isClosed() { return } - if n < 0 { - w.Errors <- os.NewSyscallError("read", errno) - continue - } if n < syscall.SizeofInotifyEvent { - w.Errors <- errors.New("inotify: short read in readEvents()") + var err error + if n == 0 { + // If EOF is received. This should really never happen. + err = io.EOF + } else if n < 0 { + // If an error occured while reading. + err = errno + } else { + // Read was too short. + err = errors.New("notify: short read in readEvents()") + } + select { + case w.Errors <- err: + case <-w.done: + return + } continue } @@ -193,7 +250,11 @@ func (w *Watcher) readEvents() { // Send the events that are not ignored on the events channel if !event.ignoreLinux(mask) { - w.Events <- event + select { + case w.Events <- event: + case <-w.done: + return + } } // Move to the next event in the buffer diff --git a/_third_party/gopkg.in/fsnotify.v1/inotify_poller.go b/_third_party/gopkg.in/fsnotify.v1/inotify_poller.go new file mode 100644 index 0000000..3b41784 --- /dev/null +++ b/_third_party/gopkg.in/fsnotify.v1/inotify_poller.go @@ -0,0 +1,186 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package fsnotify + +import ( + "errors" + "syscall" +) + +type fdPoller struct { + fd int // File descriptor (as returned by the inotify_init() syscall) + epfd int // Epoll file descriptor + pipe [2]int // Pipe for waking up +} + +func emptyPoller(fd int) *fdPoller { + poller := new(fdPoller) + poller.fd = fd + poller.epfd = -1 + poller.pipe[0] = -1 + poller.pipe[1] = -1 + return poller +} + +// Create a new inotify poller. +// This creates an inotify handler, and an epoll handler. +func newFdPoller(fd int) (*fdPoller, error) { + var errno error + poller := emptyPoller(fd) + defer func() { + if errno != nil { + poller.close() + } + }() + poller.fd = fd + + // Create epoll fd + poller.epfd, errno = syscall.EpollCreate(1) + if poller.epfd == -1 { + return nil, errno + } + // Create pipe; pipe[0] is the read end, pipe[1] the write end. + errno = syscall.Pipe2(poller.pipe[:], syscall.O_NONBLOCK) + if errno != nil { + return nil, errno + } + + // Register inotify fd with epoll + event := syscall.EpollEvent{ + Fd: int32(poller.fd), + Events: syscall.EPOLLIN, + } + errno = syscall.EpollCtl(poller.epfd, syscall.EPOLL_CTL_ADD, poller.fd, &event) + if errno != nil { + return nil, errno + } + + // Register pipe fd with epoll + event = syscall.EpollEvent{ + Fd: int32(poller.pipe[0]), + Events: syscall.EPOLLIN, + } + errno = syscall.EpollCtl(poller.epfd, syscall.EPOLL_CTL_ADD, poller.pipe[0], &event) + if errno != nil { + return nil, errno + } + + return poller, nil +} + +// Wait using epoll. +// Returns true if something is ready to be read, +// false if there is not. +func (poller *fdPoller) wait() (bool, error) { + // 3 possible events per fd, and 2 fds, makes a maximum of 6 events. + // I don't know whether epoll_wait returns the number of events returned, + // or the total number of events ready. + // I decided to catch both by making the buffer one larger than the maximum. + events := make([]syscall.EpollEvent, 7) + for { + n, errno := syscall.EpollWait(poller.epfd, events, -1) + if n == -1 { + if errno == syscall.EINTR { + continue + } + return false, errno + } + if n == 0 { + // If there are no events, try again. + continue + } + if n > 6 { + // This should never happen. More events were returned than should be possible. + return false, errors.New("epoll_wait returned more events than I know what to do with") + } + ready := events[:n] + epollhup := false + epollerr := false + epollin := false + for _, event := range ready { + if event.Fd == int32(poller.fd) { + if event.Events&syscall.EPOLLHUP != 0 { + // This should not happen, but if it does, treat it as a wakeup. + epollhup = true + } + if event.Events&syscall.EPOLLERR != 0 { + // If an error is waiting on the file descriptor, we should pretend + // something is ready to read, and let syscall.Read pick up the error. + epollerr = true + } + if event.Events&syscall.EPOLLIN != 0 { + // There is data to read. + epollin = true + } + } + if event.Fd == int32(poller.pipe[0]) { + if event.Events&syscall.EPOLLHUP != 0 { + // Write pipe descriptor was closed, by us. This means we're closing down the + // watcher, and we should wake up. + } + if event.Events&syscall.EPOLLERR != 0 { + // If an error is waiting on the pipe file descriptor. + // This is an absolute mystery, and should never ever happen. + return false, errors.New("Error on the pipe descriptor.") + } + if event.Events&syscall.EPOLLIN != 0 { + // This is a regular wakeup, so we have to clear the buffer. + err := poller.clearWake() + if err != nil { + return false, err + } + } + } + } + + if epollhup || epollerr || epollin { + return true, nil + } + return false, nil + } +} + +// Close the write end of the poller. +func (poller *fdPoller) wake() error { + buf := make([]byte, 1) + n, errno := syscall.Write(poller.pipe[1], buf) + if n == -1 { + if errno == syscall.EAGAIN { + // Buffer is full, poller will wake. + return nil + } + return errno + } + return nil +} + +func (poller *fdPoller) clearWake() error { + // You have to be woken up a LOT in order to get to 100! + buf := make([]byte, 100) + n, errno := syscall.Read(poller.pipe[0], buf) + if n == -1 { + if errno == syscall.EAGAIN { + // Buffer is empty, someone else cleared our wake. + return nil + } + return errno + } + return nil +} + +// Close all poller file descriptors, but not the one passed to it. +func (poller *fdPoller) close() { + if poller.pipe[1] != -1 { + syscall.Close(poller.pipe[1]) + } + if poller.pipe[0] != -1 { + syscall.Close(poller.pipe[0]) + } + if poller.epfd != -1 { + syscall.Close(poller.epfd) + } +} diff --git a/_third_party/gopkg.in/fsnotify.v1/integration_test.go b/_third_party/gopkg.in/fsnotify.v1/integration_test.go deleted file mode 100644 index ad51ab6..0000000 --- a/_third_party/gopkg.in/fsnotify.v1/integration_test.go +++ /dev/null @@ -1,1120 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !plan9,!solaris - -package fsnotify - -import ( - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "runtime" - "sync/atomic" - "testing" - "time" -) - -// An atomic counter -type counter struct { - val int32 -} - -func (c *counter) increment() { - atomic.AddInt32(&c.val, 1) -} - -func (c *counter) value() int32 { - return atomic.LoadInt32(&c.val) -} - -func (c *counter) reset() { - atomic.StoreInt32(&c.val, 0) -} - -// tempMkdir makes a temporary directory -func tempMkdir(t *testing.T) string { - dir, err := ioutil.TempDir("", "fsnotify") - if err != nil { - t.Fatalf("failed to create test directory: %s", err) - } - return dir -} - -// newWatcher initializes an fsnotify Watcher instance. -func newWatcher(t *testing.T) *Watcher { - watcher, err := NewWatcher() - if err != nil { - t.Fatalf("NewWatcher() failed: %s", err) - } - return watcher -} - -// addWatch adds a watch for a directory -func addWatch(t *testing.T, watcher *Watcher, dir string) { - if err := watcher.Add(dir); err != nil { - t.Fatalf("watcher.Add(%q) failed: %s", dir, err) - } -} - -func TestFsnotifyMultipleOperations(t *testing.T) { - watcher := newWatcher(t) - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Errors { - t.Fatalf("error received: %s", err) - } - }() - - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - // Create directory that's not watched - testDirToMoveFiles := tempMkdir(t) - defer os.RemoveAll(testDirToMoveFiles) - - testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile") - testFileRenamed := filepath.Join(testDirToMoveFiles, "TestFsnotifySeqRename.testfile") - - addWatch(t, watcher, testDir) - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Events - var createReceived, modifyReceived, deleteReceived, renameReceived counter - done := make(chan bool) - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) { - t.Logf("event received: %s", event) - if event.Op&Remove == Remove { - deleteReceived.increment() - } - if event.Op&Write == Write { - modifyReceived.increment() - } - if event.Op&Create == Create { - createReceived.increment() - } - if event.Op&Rename == Rename { - renameReceived.increment() - } - } else { - t.Logf("unexpected event received: %s", event) - } - } - done <- true - }() - - // Create a file - // This should add at least one event to the fsnotify event queue - var f *os.File - f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - - time.Sleep(time.Millisecond) - f.WriteString("data") - f.Sync() - f.Close() - - time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete - - if err := testRename(testFile, testFileRenamed); err != nil { - t.Fatalf("rename failed: %s", err) - } - - // Modify the file outside of the watched dir - f, err = os.Open(testFileRenamed) - if err != nil { - t.Fatalf("open test renamed file failed: %s", err) - } - f.WriteString("data") - f.Sync() - f.Close() - - time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete - - // Recreate the file that was moved - f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Close() - time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500 * time.Millisecond) - cReceived := createReceived.value() - if cReceived != 2 { - t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2) - } - mReceived := modifyReceived.value() - if mReceived != 1 { - t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1) - } - dReceived := deleteReceived.value() - rReceived := renameReceived.value() - if dReceived+rReceived != 1 { - t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", rReceived+dReceived, 1) - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() - t.Log("waiting for the event channel to become closed...") - select { - case <-done: - t.Log("event channel closed") - case <-time.After(2 * time.Second): - t.Fatal("event stream was not closed after 2 seconds") - } -} - -func TestFsnotifyMultipleCreates(t *testing.T) { - watcher := newWatcher(t) - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Errors { - t.Fatalf("error received: %s", err) - } - }() - - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile") - - addWatch(t, watcher, testDir) - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Events - var createReceived, modifyReceived, deleteReceived counter - done := make(chan bool) - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) { - t.Logf("event received: %s", event) - if event.Op&Remove == Remove { - deleteReceived.increment() - } - if event.Op&Create == Create { - createReceived.increment() - } - if event.Op&Write == Write { - modifyReceived.increment() - } - } else { - t.Logf("unexpected event received: %s", event) - } - } - done <- true - }() - - // Create a file - // This should add at least one event to the fsnotify event queue - var f *os.File - f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - - time.Sleep(time.Millisecond) - f.WriteString("data") - f.Sync() - f.Close() - - time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete - - os.Remove(testFile) - - time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete - - // Recreate the file - f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Close() - time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete - - // Modify - f, err = os.OpenFile(testFile, os.O_WRONLY, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - - time.Sleep(time.Millisecond) - f.WriteString("data") - f.Sync() - f.Close() - - time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete - - // Modify - f, err = os.OpenFile(testFile, os.O_WRONLY, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - - time.Sleep(time.Millisecond) - f.WriteString("data") - f.Sync() - f.Close() - - time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500 * time.Millisecond) - cReceived := createReceived.value() - if cReceived != 2 { - t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2) - } - mReceived := modifyReceived.value() - if mReceived < 3 { - t.Fatalf("incorrect number of modify events received after 500 ms (%d vs atleast %d)", mReceived, 3) - } - dReceived := deleteReceived.value() - if dReceived != 1 { - t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", dReceived, 1) - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() - t.Log("waiting for the event channel to become closed...") - select { - case <-done: - t.Log("event channel closed") - case <-time.After(2 * time.Second): - t.Fatal("event stream was not closed after 2 seconds") - } -} - -func TestFsnotifyDirOnly(t *testing.T) { - watcher := newWatcher(t) - - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - // Create a file before watching directory - // This should NOT add any events to the fsnotify event queue - testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile") - { - var f *os.File - f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - f.Close() - } - - addWatch(t, watcher, testDir) - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Errors { - t.Fatalf("error received: %s", err) - } - }() - - testFile := filepath.Join(testDir, "TestFsnotifyDirOnly.testfile") - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Events - var createReceived, modifyReceived, deleteReceived counter - done := make(chan bool) - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileAlreadyExists) { - t.Logf("event received: %s", event) - if event.Op&Remove == Remove { - deleteReceived.increment() - } - if event.Op&Write == Write { - modifyReceived.increment() - } - if event.Op&Create == Create { - createReceived.increment() - } - } else { - t.Logf("unexpected event received: %s", event) - } - } - done <- true - }() - - // Create a file - // This should add at least one event to the fsnotify event queue - var f *os.File - f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - - time.Sleep(time.Millisecond) - f.WriteString("data") - f.Sync() - f.Close() - - time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete - - os.Remove(testFile) - os.Remove(testFileAlreadyExists) - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500 * time.Millisecond) - cReceived := createReceived.value() - if cReceived != 1 { - t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 1) - } - mReceived := modifyReceived.value() - if mReceived != 1 { - t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1) - } - dReceived := deleteReceived.value() - if dReceived != 2 { - t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2) - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() - t.Log("waiting for the event channel to become closed...") - select { - case <-done: - t.Log("event channel closed") - case <-time.After(2 * time.Second): - t.Fatal("event stream was not closed after 2 seconds") - } -} - -func TestFsnotifyDeleteWatchedDir(t *testing.T) { - watcher := newWatcher(t) - defer watcher.Close() - - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - // Create a file before watching directory - testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile") - { - var f *os.File - f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - f.Close() - } - - addWatch(t, watcher, testDir) - - // Add a watch for testFile - addWatch(t, watcher, testFileAlreadyExists) - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Errors { - t.Fatalf("error received: %s", err) - } - }() - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Events - var deleteReceived counter - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFileAlreadyExists) { - t.Logf("event received: %s", event) - if event.Op&Remove == Remove { - deleteReceived.increment() - } - } else { - t.Logf("unexpected event received: %s", event) - } - } - }() - - os.RemoveAll(testDir) - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500 * time.Millisecond) - dReceived := deleteReceived.value() - if dReceived < 2 { - t.Fatalf("did not receive at least %d delete events, received %d after 500 ms", 2, dReceived) - } -} - -func TestFsnotifySubDir(t *testing.T) { - watcher := newWatcher(t) - - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - testFile1 := filepath.Join(testDir, "TestFsnotifyFile1.testfile") - testSubDir := filepath.Join(testDir, "sub") - testSubDirFile := filepath.Join(testDir, "sub/TestFsnotifyFile1.testfile") - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Errors { - t.Fatalf("error received: %s", err) - } - }() - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Events - var createReceived, deleteReceived counter - done := make(chan bool) - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testSubDir) || event.Name == filepath.Clean(testFile1) { - t.Logf("event received: %s", event) - if event.Op&Create == Create { - createReceived.increment() - } - if event.Op&Remove == Remove { - deleteReceived.increment() - } - } else { - t.Logf("unexpected event received: %s", event) - } - } - done <- true - }() - - addWatch(t, watcher, testDir) - - // Create sub-directory - if err := os.Mkdir(testSubDir, 0777); err != nil { - t.Fatalf("failed to create test sub-directory: %s", err) - } - - // Create a file - var f *os.File - f, err := os.OpenFile(testFile1, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - f.Close() - - // Create a file (Should not see this! we are not watching subdir) - var fs *os.File - fs, err = os.OpenFile(testSubDirFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - fs.Sync() - fs.Close() - - time.Sleep(200 * time.Millisecond) - - // Make sure receive deletes for both file and sub-directory - os.RemoveAll(testSubDir) - os.Remove(testFile1) - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500 * time.Millisecond) - cReceived := createReceived.value() - if cReceived != 2 { - t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2) - } - dReceived := deleteReceived.value() - if dReceived != 2 { - t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2) - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() - t.Log("waiting for the event channel to become closed...") - select { - case <-done: - t.Log("event channel closed") - case <-time.After(2 * time.Second): - t.Fatal("event stream was not closed after 2 seconds") - } -} - -func TestFsnotifyRename(t *testing.T) { - watcher := newWatcher(t) - - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - addWatch(t, watcher, testDir) - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Errors { - t.Fatalf("error received: %s", err) - } - }() - - testFile := filepath.Join(testDir, "TestFsnotifyEvents.testfile") - testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed") - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Events - var renameReceived counter - done := make(chan bool) - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) { - if event.Op&Rename == Rename { - renameReceived.increment() - } - t.Logf("event received: %s", event) - } else { - t.Logf("unexpected event received: %s", event) - } - } - done <- true - }() - - // Create a file - // This should add at least one event to the fsnotify event queue - var f *os.File - f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - - f.WriteString("data") - f.Sync() - f.Close() - - // Add a watch for testFile - addWatch(t, watcher, testFile) - - if err := testRename(testFile, testFileRenamed); err != nil { - t.Fatalf("rename failed: %s", err) - } - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500 * time.Millisecond) - if renameReceived.value() == 0 { - t.Fatal("fsnotify rename events have not been received after 500 ms") - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() - t.Log("waiting for the event channel to become closed...") - select { - case <-done: - t.Log("event channel closed") - case <-time.After(2 * time.Second): - t.Fatal("event stream was not closed after 2 seconds") - } - - os.Remove(testFileRenamed) -} - -func TestFsnotifyRenameToCreate(t *testing.T) { - watcher := newWatcher(t) - - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - // Create directory to get file - testDirFrom := tempMkdir(t) - defer os.RemoveAll(testDirFrom) - - addWatch(t, watcher, testDir) - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Errors { - t.Fatalf("error received: %s", err) - } - }() - - testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile") - testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed") - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Events - var createReceived counter - done := make(chan bool) - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) { - if event.Op&Create == Create { - createReceived.increment() - } - t.Logf("event received: %s", event) - } else { - t.Logf("unexpected event received: %s", event) - } - } - done <- true - }() - - // Create a file - // This should add at least one event to the fsnotify event queue - var f *os.File - f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - f.Close() - - if err := testRename(testFile, testFileRenamed); err != nil { - t.Fatalf("rename failed: %s", err) - } - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500 * time.Millisecond) - if createReceived.value() == 0 { - t.Fatal("fsnotify create events have not been received after 500 ms") - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() - t.Log("waiting for the event channel to become closed...") - select { - case <-done: - t.Log("event channel closed") - case <-time.After(2 * time.Second): - t.Fatal("event stream was not closed after 2 seconds") - } - - os.Remove(testFileRenamed) -} - -func TestFsnotifyRenameToOverwrite(t *testing.T) { - switch runtime.GOOS { - case "plan9", "windows": - t.Skipf("skipping test on %q (os.Rename over existing file does not create event).", runtime.GOOS) - } - - watcher := newWatcher(t) - - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - // Create directory to get file - testDirFrom := tempMkdir(t) - defer os.RemoveAll(testDirFrom) - - testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile") - testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed") - - // Create a file - var fr *os.File - fr, err := os.OpenFile(testFileRenamed, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - fr.Sync() - fr.Close() - - addWatch(t, watcher, testDir) - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Errors { - t.Fatalf("error received: %s", err) - } - }() - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Events - var eventReceived counter - done := make(chan bool) - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == filepath.Clean(testFileRenamed) { - eventReceived.increment() - t.Logf("event received: %s", event) - } else { - t.Logf("unexpected event received: %s", event) - } - } - done <- true - }() - - // Create a file - // This should add at least one event to the fsnotify event queue - var f *os.File - f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - f.Close() - - if err := testRename(testFile, testFileRenamed); err != nil { - t.Fatalf("rename failed: %s", err) - } - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500 * time.Millisecond) - if eventReceived.value() == 0 { - t.Fatal("fsnotify events have not been received after 500 ms") - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() - t.Log("waiting for the event channel to become closed...") - select { - case <-done: - t.Log("event channel closed") - case <-time.After(2 * time.Second): - t.Fatal("event stream was not closed after 2 seconds") - } - - os.Remove(testFileRenamed) -} - -func TestRemovalOfWatch(t *testing.T) { - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - // Create a file before watching directory - testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile") - { - var f *os.File - f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - f.Close() - } - - watcher := newWatcher(t) - defer watcher.Close() - - addWatch(t, watcher, testDir) - if err := watcher.Remove(testDir); err != nil { - t.Fatalf("Could not remove the watch: %v\n", err) - } - - go func() { - select { - case ev := <-watcher.Events: - t.Fatalf("We received event: %v\n", ev) - case <-time.After(500 * time.Millisecond): - t.Log("No event received, as expected.") - } - }() - - time.Sleep(200 * time.Millisecond) - // Modify the file outside of the watched dir - f, err := os.Open(testFileAlreadyExists) - if err != nil { - t.Fatalf("Open test file failed: %s", err) - } - f.WriteString("data") - f.Sync() - f.Close() - if err := os.Chmod(testFileAlreadyExists, 0700); err != nil { - t.Fatalf("chmod failed: %s", err) - } - time.Sleep(400 * time.Millisecond) -} - -func TestFsnotifyAttrib(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("attributes don't work on Windows.") - } - - watcher := newWatcher(t) - - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - // Receive errors on the error channel on a separate goroutine - go func() { - for err := range watcher.Errors { - t.Fatalf("error received: %s", err) - } - }() - - testFile := filepath.Join(testDir, "TestFsnotifyAttrib.testfile") - - // Receive events on the event channel on a separate goroutine - eventstream := watcher.Events - // The modifyReceived counter counts IsModify events that are not IsAttrib, - // and the attribReceived counts IsAttrib events (which are also IsModify as - // a consequence). - var modifyReceived counter - var attribReceived counter - done := make(chan bool) - go func() { - for event := range eventstream { - // Only count relevant events - if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) { - if event.Op&Write == Write { - modifyReceived.increment() - } - if event.Op&Chmod == Chmod { - attribReceived.increment() - } - t.Logf("event received: %s", event) - } else { - t.Logf("unexpected event received: %s", event) - } - } - done <- true - }() - - // Create a file - // This should add at least one event to the fsnotify event queue - var f *os.File - f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - - f.WriteString("data") - f.Sync() - f.Close() - - // Add a watch for testFile - addWatch(t, watcher, testFile) - - if err := os.Chmod(testFile, 0700); err != nil { - t.Fatalf("chmod failed: %s", err) - } - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - // Creating/writing a file changes also the mtime, so IsAttrib should be set to true here - time.Sleep(500 * time.Millisecond) - if modifyReceived.value() != 0 { - t.Fatal("received an unexpected modify event when creating a test file") - } - if attribReceived.value() == 0 { - t.Fatal("fsnotify attribute events have not received after 500 ms") - } - - // Modifying the contents of the file does not set the attrib flag (although eg. the mtime - // might have been modified). - modifyReceived.reset() - attribReceived.reset() - - f, err = os.OpenFile(testFile, os.O_WRONLY, 0) - if err != nil { - t.Fatalf("reopening test file failed: %s", err) - } - - f.WriteString("more data") - f.Sync() - f.Close() - - time.Sleep(500 * time.Millisecond) - - if modifyReceived.value() != 1 { - t.Fatal("didn't receive a modify event after changing test file contents") - } - - if attribReceived.value() != 0 { - t.Fatal("did receive an unexpected attrib event after changing test file contents") - } - - modifyReceived.reset() - attribReceived.reset() - - // Doing a chmod on the file should trigger an event with the "attrib" flag set (the contents - // of the file are not changed though) - if err := os.Chmod(testFile, 0600); err != nil { - t.Fatalf("chmod failed: %s", err) - } - - time.Sleep(500 * time.Millisecond) - - if attribReceived.value() != 1 { - t.Fatal("didn't receive an attribute change after 500ms") - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() - t.Log("waiting for the event channel to become closed...") - select { - case <-done: - t.Log("event channel closed") - case <-time.After(1e9): - t.Fatal("event stream was not closed after 1 second") - } - - os.Remove(testFile) -} - -func TestFsnotifyClose(t *testing.T) { - watcher := newWatcher(t) - watcher.Close() - - var done int32 - go func() { - watcher.Close() - atomic.StoreInt32(&done, 1) - }() - - time.Sleep(50e6) // 50 ms - if atomic.LoadInt32(&done) == 0 { - t.Fatal("double Close() test failed: second Close() call didn't return") - } - - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - if err := watcher.Add(testDir); err == nil { - t.Fatal("expected error on Watch() after Close(), got nil") - } -} - -func TestFsnotifyFakeSymlink(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("symlinks don't work on Windows.") - } - - watcher := newWatcher(t) - - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - var errorsReceived counter - // Receive errors on the error channel on a separate goroutine - go func() { - for errors := range watcher.Errors { - t.Logf("Received error: %s", errors) - errorsReceived.increment() - } - }() - - // Count the CREATE events received - var createEventsReceived, otherEventsReceived counter - go func() { - for ev := range watcher.Events { - t.Logf("event received: %s", ev) - if ev.Op&Create == Create { - createEventsReceived.increment() - } else { - otherEventsReceived.increment() - } - } - }() - - addWatch(t, watcher, testDir) - - if err := os.Symlink(filepath.Join(testDir, "zzz"), filepath.Join(testDir, "zzznew")); err != nil { - t.Fatalf("Failed to create bogus symlink: %s", err) - } - t.Logf("Created bogus symlink") - - // We expect this event to be received almost immediately, but let's wait 500 ms to be sure - time.Sleep(500 * time.Millisecond) - - // Should not be error, just no events for broken links (watching nothing) - if errorsReceived.value() > 0 { - t.Fatal("fsnotify errors have been received.") - } - if otherEventsReceived.value() > 0 { - t.Fatal("fsnotify other events received on the broken link") - } - - // Except for 1 create event (for the link itself) - if createEventsReceived.value() == 0 { - t.Fatal("fsnotify create events were not received after 500 ms") - } - if createEventsReceived.value() > 1 { - t.Fatal("fsnotify more create events received than expected") - } - - // Try closing the fsnotify instance - t.Log("calling Close()") - watcher.Close() -} - -// TestConcurrentRemovalOfWatch tests that concurrent calls to RemoveWatch do not race. -// See https://codereview.appspot.com/103300045/ -// go test -test.run=TestConcurrentRemovalOfWatch -test.cpu=1,1,1,1,1 -race -func TestConcurrentRemovalOfWatch(t *testing.T) { - if runtime.GOOS != "darwin" { - t.Skip("regression test for race only present on darwin") - } - - // Create directory to watch - testDir := tempMkdir(t) - defer os.RemoveAll(testDir) - - // Create a file before watching directory - testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile") - { - var f *os.File - f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - t.Fatalf("creating test file failed: %s", err) - } - f.Sync() - f.Close() - } - - watcher := newWatcher(t) - defer watcher.Close() - - addWatch(t, watcher, testDir) - - // Test that RemoveWatch can be invoked concurrently, with no data races. - removed1 := make(chan struct{}) - go func() { - defer close(removed1) - watcher.Remove(testDir) - }() - removed2 := make(chan struct{}) - go func() { - close(removed2) - watcher.Remove(testDir) - }() - <-removed1 - <-removed2 -} - -func testRename(file1, file2 string) error { - switch runtime.GOOS { - case "windows", "plan9": - return os.Rename(file1, file2) - default: - cmd := exec.Command("mv", file1, file2) - return cmd.Run() - } -} diff --git a/_third_party/gopkg.in/fsnotify.v1/kqueue.go b/_third_party/gopkg.in/fsnotify.v1/kqueue.go index 1636e74..265622d 100644 --- a/_third_party/gopkg.in/fsnotify.v1/kqueue.go +++ b/_third_party/gopkg.in/fsnotify.v1/kqueue.go @@ -72,16 +72,20 @@ func (w *Watcher) Close() error { w.isClosed = true w.mu.Unlock() - // Send "quit" message to the reader goroutine: - w.done <- true - w.mu.Lock() ws := w.watches w.mu.Unlock() + + var err error for name := range ws { - w.Remove(name) + if e := w.Remove(name); e != nil && err == nil { + err = e + } } + // Send "quit" message to the reader goroutine: + w.done <- true + return nil }