예제 #1
0
파일: source_views.py 프로젝트: gxf1986/hub
 def render(
     self,
     request: HttpRequest,
     editing_context: SourceEditContext,
     extra_context: typing.Optional[dict] = None,
 ) -> HttpResponse:
     render_context = {
         "project": self.project,
         "has_edit_permission":
         self.has_permission(ProjectPermissionType.EDIT),
         "file_name": utf8_basename(editing_context.path),
         "file_directory": utf8_dirname(editing_context.path),
         "file_path": editing_context.path,
         "file_extension": editing_context.extension,
         "file_content": editing_context.content,
         "file_editable": editing_context.editable,
         "source": editing_context.source,
         "supports_commit_message": editing_context.supports_commit_message,
     }
     render_context.update(extra_context or {})
     return render(
         request,
         "projects/source_open.html",
         self.get_render_context(render_context),
     )
예제 #2
0
def sources_in_directory(
    directory: typing.Optional[str],
    sources: typing.Iterable[Source],
    authentication: LinkedSourceAuthentication,
) -> typing.Iterable[DirectoryListEntry]:
    """Yield a `DirectoryListEntry` for each `Source` in `sources` if the `Source` is inside the `directory`."""
    directory = directory or ""
    seen_directories: typing.Set[str] = set()

    for source in sources:
        if isinstance(source, GithubSource):
            facade = GitHubFacade(source.repo, authentication.github_token)
            try:
                for entry in iterate_github_source(directory, source, facade):
                    if (entry.type == DirectoryEntryType.DIRECTORY
                            or entry.type == DirectoryEntryType.LINKED_SOURCE):
                        if entry.name in seen_directories:
                            continue
                        seen_directories.add(entry.name)
                    yield entry
                continue
            except IncorrectDirectoryException:
                pass

        if utf8_dirname(source.path) != directory:
            continue

        entry = make_directory_entry(directory, source)

        if (entry.type == DirectoryEntryType.DIRECTORY
                or entry.type == DirectoryEntryType.LINKED_SOURCE):
            if entry.name in seen_directories:
                continue
            seen_directories.add(entry.name)
        yield entry
예제 #3
0
파일: shared.py 프로젝트: gxf1986/hub
def get_source(user: User, project: Project,
               path: str) -> typing.Union[Source, DiskSource]:
    """
    Locate a `Source` that contains the file at `path`.

    Iterate up through the directory tree of the path until Source(s) mapped to that path are found. Then check if that
    file exists in the source.

    Fall back to DiskSource if no file is found in any linked Sources.
    """
    # the paths won't match if the incoming path has a trailing slash as the paths on the source doesn't (shouldn't)
    path = path.rstrip("/")

    original_path = path

    gh_facade: typing.Optional[GitHubFacade] = None

    while True:
        sources = Source.objects.filter(path=path, project=project)

        if sources:
            if path == original_path:
                # This source should be one without sub files
                return sources[0]
            else:
                # These may be Github, etc, sources mapped into the same directory. Find one that has a file at the path

                for source in sources:
                    if isinstance(source, GithubSource):
                        source = typing.cast(GithubSource, source)

                        if gh_facade is None:
                            gh_token = user_github_token(user)
                            gh_facade = GitHubFacade(source.repo, gh_token)

                        relative_path = utf8_path_join(
                            source.subpath,
                            strip_directory(original_path, source.path))
                        if gh_facade.path_exists(relative_path):
                            return source  # this source has the file so it must be this one?

                        # if not, continue on, keep going up the tree to find the root source
                    else:
                        raise RuntimeError(
                            "Don't know how to examine the contents of {}".
                            format(type(source)))

        if path == ".":
            break
        path = utf8_dirname(path)
        if path == "/" or path == "":
            path = "."

    # Fall Back to DiskSource
    return DiskSource()
예제 #4
0
파일: source_views.py 프로젝트: gxf1986/hub
    def post(self, request: HttpRequest, account_name: str,
             project_name: str) -> HttpResponse:  # type: ignore
        project = self.get_project(request.user, account_name, project_name)

        body = json.loads(request.body)

        source_id = body["source_id"]
        source_path = body["source_path"]
        target_id = body["target_type"]
        target_name = body["target_name"]

        if "/" in target_name:
            raise ValueError("Target name can not contain /")

        if source_id:
            source = get_object_or_404(Source, project=project, pk=source_id)
        else:
            source = DiskSource()

        scf = make_source_content_facade(request.user, source_path, source,
                                         project)

        target_path = utf8_path_join(utf8_dirname(source_path), target_name)
        target_type = ConversionFormatId.from_id(target_id)

        try:
            self.source_convert(request, project, scf, target_path,
                                target_name, target_type)
        except oauth2client.client.Error:
            return JsonResponse({
                "success":
                False,
                "error":
                "Could not authenticate with your Google account."
                "Please try logging into Stencila Hub with "
                "your Google account to refresh the token.",
            })
        except RuntimeError:
            return JsonResponse({
                "success":
                False,
                "error":
                "Conversion of your document failed. Please check the Project Activity page for more "
                "information.",
            })

        for message in scf.message_iterator():
            messages.add_message(request, message.level, message.message)

        messages.success(
            request, "{} was converted.".format(utf8_basename(source_path)))

        return JsonResponse({"success": True})
예제 #5
0
    def create_file(self, relative_path: str) -> None:
        full_path = self.full_file_path(relative_path)
        if utf8_path_exists(full_path):
            raise OSError(
                "Can not create project file at {} as it already exists".format(
                    full_path
                )
            )

        utf8_makedirs(utf8_dirname(full_path), exist_ok=True)

        with open(full_path, "a"):
            pass
예제 #6
0
파일: mixins.py 프로젝트: gxf1986/hub
    def convert_to_google_docs(
        self,
        request: HttpRequest,
        project: Project,
        scf: SourceContentFacade,
        target_name: str,
        target_path: str,
    ) -> None:
        """
        Convert a document to Google Docs.

        If the document is already in DOCX or HTML format it will just be uploaded, otherwise it is first converted to
        DOCX. The document is uploaded in DOCX/HTML and Google takes care of converting to Google Docs format.
        """
        if scf.source_type not in (ConversionFormatId.html,
                                   ConversionFormatId.docx):
            output_content, output_mime_type = self.convert_source_for_google_docs(
                project, request.user, scf)
        else:
            output_mime_type = (mimetype_from_path(scf.file_path)
                                or "application/octet-stream")
            output_content = scf.get_binary_content()
        gdf = scf.google_docs_facade

        if gdf is None:
            raise TypeError(
                "Google Docs Facade was not set up. Check that app tokens are good."
            )

        new_doc_id = gdf.create_document(target_name, output_content,
                                         output_mime_type)
        existing_source = GoogleDocsSource.objects.filter(
            project=project, path=target_path).first()
        new_source = gdf.create_source_from_document(project,
                                                     utf8_dirname(target_path),
                                                     new_doc_id)
        if existing_source is not None:
            gdf.trash_document(existing_source.doc_id)
            messages.info(
                request,
                'Existing Google Docs file "{}" was moved to the Trash.'.
                format(target_name),
            )
            existing_source.doc_id = new_source.doc_id
            existing_source.save()
        else:
            new_source.save()
예제 #7
0
    def move_file(self, current_relative_path: str, new_relative_path: str) -> None:
        current_path = self.full_file_path(current_relative_path)
        new_path = self.full_file_path(new_relative_path)

        if utf8_isdir(new_path):
            # path moving to is a directory so actually move inside the path
            filename = utf8_basename(current_path)
            new_path = utf8_path_join(new_path, filename)

        if utf8_path_exists(new_path):
            raise OSError(
                "Can not move {} to {} as target file exists.".format(
                    current_relative_path, new_relative_path
                )
            )

        utf8_makedirs(utf8_dirname(new_path), exist_ok=True)
        utf8_rename(current_path, new_path)
예제 #8
0
파일: source_views.py 프로젝트: gxf1986/hub
    def process_get(
        self,
        account_name: str,
        project_name: str,
        path: str,
        content_facade: SourceContentFacade,
    ) -> HttpResponse:
        # TODO: see if this can return a handle for streaming response
        file_content = content_facade.get_binary_content()

        if content_facade.error_exists:
            content_facade.add_messages_to_request(self.request)
            return project_files_redirect(account_name, project_name,
                                          utf8_dirname(path))

        response = HttpResponse(file_content,
                                content_type="application/octet-stream")

        response["Content-Disposition"] = "attachment; filename={}".format(
            content_facade.get_name())
        return response
예제 #9
0
파일: source_views.py 프로젝트: gxf1986/hub
    def process_get(
        self,
        request: HttpRequest,
        account_name: str,
        project_name: str,
        path: str,
        content_facade: SourceContentFacade,
    ) -> HttpResponse:
        edit_context = content_facade.get_edit_context()

        if edit_context is None:
            content_facade.add_messages_to_request(request)
            return project_files_redirect(account_name, project_name,
                                          utf8_dirname(path))

        return self.render(
            request,
            edit_context,
            {
                "default_commit_message":
                self.get_default_commit_message(request)
            },
        )
예제 #10
0
    def get(  # type: ignore
        self,
        request: HttpRequest,
        account_name: str,
        project_name: str,
        path: str,
    ) -> HttpResponse:
        project = self.get_project(request.user, account_name, project_name)
        source = get_source(request.user, project, path)

        scf = make_source_content_facade(request.user, path, source, project)

        # the isinstance check is because Source might be a DiskSource which is not a subclass of Source
        pi, created = PublishedItem.objects.get_or_create(
            project=project,
            source_path=path,
            source=source if isinstance(source, Source) else None,
            snapshot=None,
        )

        if created or scf.source_modification_time > pi.updated or not pi.path:
            try:
                self.convert_and_publish(request.user, project, pi, created,
                                         source, path, scf)
            except (NonFileError, UnknownMimeTypeError, RuntimeError) as e:
                filename = utf8_basename(path)
                directory = utf8_dirname(path)

                # by default, no message since the user might have just entered a bad URL
                message_format: typing.Optional[str] = None

                if isinstance(e, RuntimeError):
                    message_format = (
                        "Unable to preview <em>{}</em> as it could not be converted to HTML. Please "
                        "check the Project Activity page for more information."
                    )
                elif isinstance(e, UnknownMimeTypeError):
                    message_format = "Unable to preview <em>{}</em> as its file type could not be determined."
                    messages.error(
                        request,
                        "Unable to preview <em>{}</em> as its file type could not be determined."
                        .format(escape(utf8_basename(path))),
                        extra_tags="safe",
                    )

                if message_format:
                    messages.error(
                        request,
                        message_format.format(escape(filename)),
                        extra_tags="safe",
                    )
                if directory:
                    return redirect("project_files_path", account_name,
                                    project, directory)
                return redirect("project_files", account_name, project_name)

        return published_item_render(
            request,
            pi,
            reverse(
                "file_source_download",
                args=(project.account.name, project.name, pi.source_path),
            ),
            "HTML Preview of {}".format(pi.source_path),
        )
예제 #11
0
 def media_path(self, path: str) -> str:
     media_path = "{}.html.media/{}".format(self.pk, path)
     return relative_path_join(utf8_dirname(self.path), media_path)
예제 #12
0
파일: source_views.py 프로젝트: gxf1986/hub
    def perform_post(
        self,
        request: HttpRequest,
        account_name: str,
        project_name: str,
        path: str,
        content_facade: SourceContentFacade,
    ) -> HttpResponse:
        commit_message = request.POST.get(
            "commit_message") or self.get_default_commit_message(request)

        storage_limit = account_resource_limit(self.project.account,
                                               QuotaName.STORAGE_LIMIT)

        update_success = None
        content_override = None

        if storage_limit != -1 and isinstance(content_facade.source,
                                              DiskSource):
            old_size = content_facade.get_size()
            new_size = len(request.POST["file_content"])
            if new_size > old_size:
                storage_used = (content_facade.disk_file_facade.
                                get_project_directory_size())
                is_account_admin = user_is_account_admin(
                    self.request.user, self.project.account)
                subscription_upgrade_text = get_subscription_upgrade_text(
                    is_account_admin, self.project.account)

                if (new_size - old_size) + storage_used > storage_limit:
                    message = (
                        "The file content could not be saved as it would exceed the storage limit for the "
                        "account <em>{}</em>. {}".format(
                            escape(self.project.account),
                            subscription_upgrade_text))
                    messages.error(request, message, extra_tags="safe")
                    update_success = False
                    content_override = request.POST["file_content"]

        if update_success is None:
            update_success = content_facade.update_content(
                request.POST["file_content"], commit_message)

        error_exists = content_facade.error_exists

        content_facade.add_messages_to_request(request)

        dirname = utf8_dirname(path)

        if error_exists or not update_success:
            edit_context = content_facade.get_edit_context(content_override)

            if edit_context is None or content_facade.error_exists:
                return project_files_redirect(account_name, project_name,
                                              dirname)

            return self.render(
                request,
                edit_context,
                {
                    "commit_message":
                    commit_message,
                    "default_commit_message":
                    self.get_default_commit_message(request),
                },
            )

        messages.success(
            request, "Content of {} updated.".format(os.path.basename(path)))

        return project_files_redirect(account_name, project_name, dirname)
예제 #13
0
    def get(  # type: ignore
        self,
        request: HttpRequest,
        account_name: str,
        project_name: str,
        version: int,
        path: str,
    ) -> HttpResponse:
        snapshot, file_path = self.get_snapshot_and_path(
            request, account_name, project_name, version, path)
        if file_path is None:
            raise TypeError(
                "Can't work with None path. But this won't happen unless path being passed is None."
            )

        pi, created = PublishedItem.objects.get_or_create(project=self.project,
                                                          snapshot=snapshot,
                                                          source_path=path)

        published_path = utf8_path_join(
            generate_snapshot_publish_directory(settings.STORAGE_DIR,
                                                snapshot),
            "{}.html".format(pi.pk),
        )

        if created or not pi.path:
            # don't bother checking modification time since snapshots shouldn't change
            try:
                source_type = conversion_format_from_path(file_path)
                self.do_conversion(
                    snapshot.project,
                    request.user,
                    source_type,
                    file_path,
                    ConversionFormatId.html,
                    published_path,
                    False,
                )
                pi.path = published_path
                pi.save()
            except Exception as e:
                if created:
                    pi.delete()

                if not isinstance(e, (UnknownMimeTypeError, RuntimeError)):
                    raise

                filename = escape(utf8_basename(path))

                if isinstance(e, UnknownMimeTypeError):
                    error_format = "Unable to preview <em>{}</em> as its file type could not be determined."
                else:
                    error_format = (
                        "Unable to preview <em>{}</em> as it could not be converted to HTML. Please "
                        "check the Project Activity page for more information."
                    )

                messages.error(request,
                               error_format.format(filename),
                               extra_tags="safe")

                dirname = utf8_dirname(path)
                redirect_args = [
                    snapshot.project.account.name,
                    snapshot.project.name,
                    snapshot.version_number,
                ]

                if dirname:
                    redirect_args.append(dirname)
                    redirect_view = "snapshot_files_path"
                else:
                    redirect_view = "snapshot_files"
                return redirect(redirect_view, *redirect_args)

        project = self.project
        return published_item_render(
            request,
            pi,
            reverse(
                "api-snapshots-retrieve-file",
                args=(
                    project.id,
                    snapshot.number,
                    pi.source_path,
                ),
            ),
            "HTML Preview of {}".format(pi.source_path),
        )