Skip to content

Commit

Permalink
feat: support browse files on web (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
monkeyWie authored Nov 29, 2023
1 parent ee7da17 commit db15650
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 69 deletions.
7 changes: 4 additions & 3 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import (
// only for local development
func main() {
cfg := &model.StartConfig{
Network: "tcp",
Address: "127.0.0.1:9999",
Storage: model.StorageBolt,
Network: "tcp",
Address: "127.0.0.1:9999",
Storage: model.StorageBolt,
WebEnable: true,
}
cmd.Start(cfg)
}
9 changes: 9 additions & 0 deletions internal/fetcher/fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ func (m *FetcherMeta) SingleFilepath() string {
return path.Join(m.Opts.Path, file.Path, fileName)
}

// RootDirPath return the root dir path of the task file.
func (m *FetcherMeta) RootDirPath() string {
if m.Res.Name != "" {
return m.FolderPath()
} else {
return m.Opts.Path
}
}

// FetcherBuilder defines the interface for a fetcher builder.
type FetcherBuilder interface {
// Schemes returns the schemes supported by the fetcher.
Expand Down
13 changes: 1 addition & 12 deletions pkg/download/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,18 +540,7 @@ func (d *Downloader) GetTask(id string) *Task {
}

func (d *Downloader) GetTasks() []*Task {
tasks := make([]*Task, len(d.tasks))
for i := 0; i < len(d.tasks); i++ {
tasks[i] = d.tasks[i]
}
// sort tasks by status, order by Status(if(running,1,0)) desc, CreatedAt desc
sort.Slice(tasks, func(i, j int) bool {
if tasks[i].Status != tasks[j].Status {
return tasks[i].Status == base.DownloadStatusRunning
}
return tasks[i].CreatedAt.After(tasks[j].CreatedAt)
})
return tasks
return d.tasks
}

func (d *Downloader) GetConfig() (*DownloaderStoreConfig, error) {
Expand Down
51 changes: 39 additions & 12 deletions pkg/rest/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,10 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
r.Methods(http.MethodDelete).Path("/api/v1/extensions/{identity}").HandlerFunc(DeleteExtension)
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}/update").HandlerFunc(UpdateCheckExtension)
r.Methods(http.MethodPost).Path("/api/v1/extensions/{identity}/update").HandlerFunc(UpdateExtension)
r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem)))
r.Path("/api/v1/proxy").HandlerFunc(DoProxy)
if startCfg.WebEnable {
r.PathPrefix("/fs/tasks").Handler(http.FileServer(new(taskFileSystem)))
r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem)))
r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS)))
}

Expand Down Expand Up @@ -138,30 +139,56 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
return srv, listener, nil
}

// handle extension file resource
type extensionFileSystem struct {
}

func (e *extensionFileSystem) Open(name string) (http.File, error) {
func resolvePath(urlPath string, prefix string) (identity string, path string, err error) {
// remove prefix
path := strings.TrimPrefix(name, "/fs/extensions")
clearPath := strings.TrimPrefix(urlPath, prefix)
// match extension identity, eg: /fs/extensions/identity/xxx
reg := regexp.MustCompile(`^/([^/]+)/(.*)$`)
if !reg.MatchString(path) {
return nil, os.ErrNotExist
if !reg.MatchString(clearPath) {
err = os.ErrNotExist
return
}
matched := reg.FindStringSubmatch(path)
matched := reg.FindStringSubmatch(clearPath)
if len(matched) != 3 {
err = os.ErrNotExist
return
}
return matched[1], matched[2], nil
}

// handle task file resource
type taskFileSystem struct {
}

func (e *taskFileSystem) Open(name string) (http.File, error) {
// get extension identity
identity, path, err := resolvePath(name, "/fs/tasks")
if err != nil {
return nil, err
}
task := Downloader.GetTask(identity)
if task == nil {
return nil, os.ErrNotExist
}
return os.Open(filepath.Join(task.Meta.RootDirPath(), path))
}

// handle extension file resource
type extensionFileSystem struct {
}

func (e *extensionFileSystem) Open(name string) (http.File, error) {
// get extension identity
identity := matched[1]
identity, path, err := resolvePath(name, "/fs/extensions")
if err != nil {
return nil, err
}
extension, err := Downloader.GetExtension(identity)
if err != nil {
return nil, os.ErrNotExist
}
extensionPath := Downloader.ExtensionPath(extension)
return os.Open(filepath.Join(extensionPath, matched[2]))
return os.Open(filepath.Join(extensionPath, path))
}

func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
Expand Down
1 change: 1 addition & 0 deletions pkg/rest/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ func doTest(handler func()) {
cfg.Init()
cfg.Storage = storage
cfg.StorageDir = ".test_storage"
cfg.WebEnable = true
fileListener := doStart(cfg)
defer func() {
if err := fileListener.Close(); err != nil {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@

import 'package:gopeed/app/modules/task/controllers/task_list_controller.dart';

import '../../../../api/model/task.dart';

class TaskDownloadedController extends TaskListController {
TaskDownloadedController() : super([Status.done]);
TaskDownloadedController() : super([Status.done], SortDirection.asc);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ class TaskDownloadingController extends TaskListController {
Status.pause,
Status.wait,
Status.error
]);
], SortDirection.desc);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import 'package:get/get.dart';
import '../../../../api/api.dart';
import '../../../../api/model/task.dart';

enum SortDirection { asc, desc }

abstract class TaskListController extends GetxController {
List<Status> statuses;
SortDirection sortDirection;

TaskListController(this.statuses);
TaskListController(this.statuses, this.sortDirection);

final tasks = <Task>[].obs;
final isRunning = false.obs;
Expand Down Expand Up @@ -43,6 +46,15 @@ abstract class TaskListController extends GetxController {
}

getTasksState() async {
tasks.value = await getTasks(statuses);
final tasks = await getTasks(statuses);
// sort tasks by create time
tasks.sort((a, b) {
if (sortDirection == SortDirection.asc) {
return a.createdAt.compareTo(b.createdAt);
} else {
return b.createdAt.compareTo(a.createdAt);
}
});
this.tasks.value = tasks;
}
}
62 changes: 43 additions & 19 deletions ui/flutter/lib/app/modules/task/views/task_files_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import 'package:get/get.dart';
import 'package:open_file/open_file.dart';
import 'package:path/path.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';

import '../../../../api/api.dart' as api;
import '../../../../util/browser_download/browser_download.dart';
import '../../../../util/file_icon.dart';
import '../../../../util/icons.dart';
import '../../../../util/util.dart';
Expand All @@ -21,7 +24,7 @@ class TaskFilesView extends GetView<TaskFilesController> {
icon: const Icon(Icons.arrow_back),
onPressed: () => Get.rootDelegate.popRoute()),
// actions: [],
title: Obx(() => Text(controller.task.value!.meta.res!.name)),
title: Obx(() => Text(controller.task.value?.meta.res?.name ?? "")),
),
body: Obx(() {
final fileList = controller.fileList;
Expand Down Expand Up @@ -53,11 +56,12 @@ class TaskFilesView extends GetView<TaskFilesController> {
itemBuilder: (context, index) {
final meta = controller.task.value!.meta;
final file = fileList[index];
final filePath = Util.safePathJoin([
meta.opts.path,
meta.res!.name,
file.filePath(meta.opts.name)
]);
// if resource is single file, use opts.name as file name
final realFileName =
meta.res!.name.isEmpty ? file.name : "";
final fileRelativePath = file.filePath(realFileName);
final filePath = Util.safePathJoin(
[meta.opts.path, meta.res!.name, fileRelativePath]);
final fileName = basename(filePath);
return ListTile(
leading: file.isDirectory
Expand All @@ -77,19 +81,39 @@ class TaskFilesView extends GetView<TaskFilesController> {
width: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: const Icon(Icons.play_circle),
onPressed: () {
OpenFile.open(filePath);
}),
IconButton(
icon: const Icon(Icons.share),
onPressed: () {
final xfile = XFile(filePath);
Share.shareXFiles([xfile]);
})
],
children: Util.isWeb()
? () {
final accessUrl = api.join(
"/fs/tasks/${controller.task.value!.id}$fileRelativePath");
return [
IconButton(
icon:
const Icon(Icons.play_circle),
onPressed: () {
launchUrl(Uri.parse(accessUrl),
webOnlyWindowName:
"_blank");
}),
IconButton(
icon: const Icon(Icons.download),
onPressed: () {
download(accessUrl, fileName);
})
];
}()
: [
IconButton(
icon: const Icon(Icons.play_circle),
onPressed: () {
OpenFile.open(filePath);
}),
IconButton(
icon: const Icon(Icons.share),
onPressed: () {
final xfile = XFile(filePath);
Share.shareXFiles([xfile]);
})
],
),
),
onTap: () {
Expand Down
8 changes: 4 additions & 4 deletions ui/flutter/lib/app/modules/task/views/task_view.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:gopeed/app/modules/task/controllers/task_downloading_controller.dart';
import '../controllers/task_downloaded_controller.dart';
import 'task_downloading_view.dart';

import 'task_downloaded_view.dart';
import '../controllers/task_controller.dart';
import '../controllers/task_downloaded_controller.dart';
import '../controllers/task_downloading_controller.dart';
import 'task_downloaded_view.dart';
import 'task_downloading_view.dart';

class TaskView extends GetView<TaskController> {
const TaskView({Key? key}) : super(key: key);
Expand Down
26 changes: 12 additions & 14 deletions ui/flutter/lib/app/views/buid_task_list_view.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:gopeed/api/model/meta.dart';
import 'package:path/path.dart' as path;
import 'package:styled_widget/styled_widget.dart';

import '../../api/api.dart';
import '../../api/model/meta.dart';
import '../../api/model/task.dart';
import '../../util/file_explorer.dart';
import '../../util/file_icon.dart';
Expand Down Expand Up @@ -110,19 +110,17 @@ class BuildTaskListView extends GetView {
List<Widget> buildActions() {
final list = <Widget>[];
if (isDone()) {
if (Util.isDesktop() || Util.isMobile()) {
list.add(IconButton(
icon: const Icon(Icons.folder_open),
onPressed: () {
if (Util.isDesktop()) {
FileExplorer.openAndSelectFile(buildExplorerUrl(task));
} else {
Get.rootDelegate
.toNamed(Routes.TASK_FILES, parameters: {'id': task.id});
}
},
));
}
list.add(IconButton(
icon: const Icon(Icons.folder_open),
onPressed: () {
if (Util.isDesktop()) {
FileExplorer.openAndSelectFile(buildExplorerUrl(task));
} else {
Get.rootDelegate
.toNamed(Routes.TASK_FILES, parameters: {'id': task.id});
}
},
));
} else {
if (isRunning()) {
list.add(IconButton(
Expand Down
4 changes: 4 additions & 0 deletions ui/flutter/lib/util/browser_download/browser_download.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import 'browser_download_stub.dart'
if (dart.library.html) 'entry/browser_download_browser.dart';

void download(String url, String name) => doDownload(url, name);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
void doDownload(String url, String name) => throw UnimplementedError();
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// ignore: avoid_web_libraries_in_flutter
import 'dart:html' as html;

void doDownload(String url, String name) {
final anchorElement = html.AnchorElement(href: url);
anchorElement.download = name;
anchorElement.target = '_blank';
anchorElement.click();
}

0 comments on commit db15650

Please sign in to comment.