mirror of
https://github.com/samuelncui/yatm.git
synced 2026-08-28 20:06:00 +00:00
feat: add rollback and virtual list
This commit is contained in:
@@ -16,9 +16,5 @@ func (api *API) JobCreate(ctx context.Context, req *entity.JobCreateRequest) (*e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := api.exe.Start(ctx, job); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.JobCreateReply{Job: convertJobs(job)[0]}, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
)
|
||||
|
||||
func (api *API) JobEditState(ctx context.Context, req *entity.JobEditStateRequest) (*entity.JobEditStateReply, error) {
|
||||
job, err := api.exe.GetJob(ctx, req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if job == nil {
|
||||
return nil, fmt.Errorf("job not found, id= %d", req.Id)
|
||||
}
|
||||
|
||||
if job.Status == entity.JobStatus_PROCESSING {
|
||||
return nil, fmt.Errorf("job status 'PROCESSING' is unexpected")
|
||||
}
|
||||
if req.Status != nil {
|
||||
if *req.Status == entity.JobStatus_PROCESSING {
|
||||
return nil, fmt.Errorf("job target status 'PROCESSING' is unexpected")
|
||||
}
|
||||
job.Status = *req.Status
|
||||
}
|
||||
|
||||
job.State = req.State
|
||||
if _, err := api.exe.SaveJob(ctx, job); err != nil {
|
||||
return nil, fmt.Errorf("save job fail, %w", err)
|
||||
}
|
||||
|
||||
executor, err := api.exe.GetJobExecutor(ctx, job.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get job executor fail, %w", err)
|
||||
}
|
||||
|
||||
if err := executor.Close(ctx); err != nil {
|
||||
return nil, fmt.Errorf("close job executor fail, %w", err)
|
||||
}
|
||||
|
||||
return &entity.JobEditStateReply{}, nil
|
||||
}
|
||||
+3
-8
@@ -6,15 +6,10 @@ import (
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
)
|
||||
|
||||
func (api *API) JobNext(ctx context.Context, req *entity.JobNextRequest) (*entity.JobNextReply, error) {
|
||||
job, err := api.exe.GetJob(ctx, req.Id)
|
||||
if err != nil {
|
||||
func (api *API) JobDispatch(ctx context.Context, req *entity.JobDispatchRequest) (*entity.JobDispatchReply, error) {
|
||||
if err := api.exe.Dispatch(ctx, req.Id, req.Param); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := api.exe.Submit(ctx, job, req.Param); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.JobNextReply{Job: convertJobs(job)[0]}, nil
|
||||
return &entity.JobDispatchReply{}, nil
|
||||
}
|
||||
|
||||
+84
-83
@@ -328,19 +328,19 @@ func (*JobState_Archive) isJobState_State() {}
|
||||
|
||||
func (*JobState_Restore) isJobState_State() {}
|
||||
|
||||
type JobNextParam struct {
|
||||
type JobDispatchParam struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Param:
|
||||
// *JobNextParam_Archive
|
||||
// *JobNextParam_Restore
|
||||
Param isJobNextParam_Param `protobuf_oneof:"param"`
|
||||
// *JobDispatchParam_Archive
|
||||
// *JobDispatchParam_Restore
|
||||
Param isJobDispatchParam_Param `protobuf_oneof:"param"`
|
||||
}
|
||||
|
||||
func (x *JobNextParam) Reset() {
|
||||
*x = JobNextParam{}
|
||||
func (x *JobDispatchParam) Reset() {
|
||||
*x = JobDispatchParam{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_job_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -348,13 +348,13 @@ func (x *JobNextParam) Reset() {
|
||||
}
|
||||
}
|
||||
|
||||
func (x *JobNextParam) String() string {
|
||||
func (x *JobDispatchParam) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*JobNextParam) ProtoMessage() {}
|
||||
func (*JobDispatchParam) ProtoMessage() {}
|
||||
|
||||
func (x *JobNextParam) ProtoReflect() protoreflect.Message {
|
||||
func (x *JobDispatchParam) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_job_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -366,47 +366,47 @@ func (x *JobNextParam) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use JobNextParam.ProtoReflect.Descriptor instead.
|
||||
func (*JobNextParam) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use JobDispatchParam.ProtoReflect.Descriptor instead.
|
||||
func (*JobDispatchParam) Descriptor() ([]byte, []int) {
|
||||
return file_job_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (m *JobNextParam) GetParam() isJobNextParam_Param {
|
||||
func (m *JobDispatchParam) GetParam() isJobDispatchParam_Param {
|
||||
if m != nil {
|
||||
return m.Param
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *JobNextParam) GetArchive() *JobArchiveNextParam {
|
||||
if x, ok := x.GetParam().(*JobNextParam_Archive); ok {
|
||||
func (x *JobDispatchParam) GetArchive() *JobArchiveDispatchParam {
|
||||
if x, ok := x.GetParam().(*JobDispatchParam_Archive); ok {
|
||||
return x.Archive
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *JobNextParam) GetRestore() *JobRestoreNextParam {
|
||||
if x, ok := x.GetParam().(*JobNextParam_Restore); ok {
|
||||
func (x *JobDispatchParam) GetRestore() *JobRestoreDispatchParam {
|
||||
if x, ok := x.GetParam().(*JobDispatchParam_Restore); ok {
|
||||
return x.Restore
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isJobNextParam_Param interface {
|
||||
isJobNextParam_Param()
|
||||
type isJobDispatchParam_Param interface {
|
||||
isJobDispatchParam_Param()
|
||||
}
|
||||
|
||||
type JobNextParam_Archive struct {
|
||||
Archive *JobArchiveNextParam `protobuf:"bytes,1,opt,name=archive,proto3,oneof"`
|
||||
type JobDispatchParam_Archive struct {
|
||||
Archive *JobArchiveDispatchParam `protobuf:"bytes,1,opt,name=archive,proto3,oneof"`
|
||||
}
|
||||
|
||||
type JobNextParam_Restore struct {
|
||||
Restore *JobRestoreNextParam `protobuf:"bytes,2,opt,name=restore,proto3,oneof"`
|
||||
type JobDispatchParam_Restore struct {
|
||||
Restore *JobRestoreDispatchParam `protobuf:"bytes,2,opt,name=restore,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*JobNextParam_Archive) isJobNextParam_Param() {}
|
||||
func (*JobDispatchParam_Archive) isJobDispatchParam_Param() {}
|
||||
|
||||
func (*JobNextParam_Restore) isJobNextParam_Param() {}
|
||||
func (*JobDispatchParam_Restore) isJobDispatchParam_Param() {}
|
||||
|
||||
type CreatableJob struct {
|
||||
state protoimpl.MessageState
|
||||
@@ -697,57 +697,58 @@ var file_job_proto_rawDesc = []byte{
|
||||
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74,
|
||||
0x6f, 0x72, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x07,
|
||||
0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x0c, 0x4a, 0x6f, 0x62, 0x4e,
|
||||
0x65, 0x78, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x3c, 0x0a, 0x07, 0x61, 0x72, 0x63, 0x68,
|
||||
0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6a, 0x6f, 0x62, 0x5f,
|
||||
0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69,
|
||||
0x76, 0x65, 0x4e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x48, 0x00, 0x52, 0x07, 0x61,
|
||||
0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65,
|
||||
0x4e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x73,
|
||||
0x74, 0x6f, 0x72, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x4f, 0x0a,
|
||||
0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||
0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x05, 0x70, 0x61, 0x72,
|
||||
0x61, 0x6d, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6a, 0x6f, 0x62, 0x2e, 0x4a,
|
||||
0x6f, 0x62, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x52, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x90,
|
||||
0x01, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x06,
|
||||
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x6a,
|
||||
0x6f, 0x62, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x00, 0x52, 0x06,
|
||||
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d,
|
||||
0x69, 0x74, 0x18, 0x21, 0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69,
|
||||
0x74, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x22,
|
||||
0x20, 0x01, 0x28, 0x03, 0x48, 0x02, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x88, 0x01,
|
||||
0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x08, 0x0a, 0x06,
|
||||
0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65,
|
||||
0x74, 0x22, 0x7f, 0x0a, 0x17, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x6c, 0x79,
|
||||
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x0f,
|
||||
0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x73, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53,
|
||||
0x69, 0x6e, 0x63, 0x65, 0x4e, 0x73, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d,
|
||||
0x69, 0x74, 0x18, 0x21, 0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69,
|
||||
0x74, 0x88, 0x01, 0x01, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f,
|
||||
0x73, 0x69, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d,
|
||||
0x69, 0x74, 0x22, 0x8f, 0x01, 0x0a, 0x0a, 0x4a, 0x6f, 0x62, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61,
|
||||
0x79, 0x12, 0x3a, 0x0a, 0x07, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65,
|
||||
0x2e, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c,
|
||||
0x61, 0x79, 0x48, 0x00, 0x52, 0x07, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x3a, 0x0a,
|
||||
0x07, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e,
|
||||
0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x4a, 0x6f, 0x62,
|
||||
0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x48, 0x00,
|
||||
0x52, 0x07, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x64, 0x69, 0x73,
|
||||
0x70, 0x6c, 0x61, 0x79, 0x2a, 0x6b, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x52, 0x41, 0x46, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09,
|
||||
0x4e, 0x4f, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x59, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x50,
|
||||
0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x43,
|
||||
0x45, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50,
|
||||
0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45,
|
||||
0x44, 0x10, 0x7f, 0x12, 0x0c, 0x0a, 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0xff,
|
||||
0x01, 0x42, 0x23, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
||||
0x73, 0x61, 0x6d, 0x75, 0x65, 0x6c, 0x6e, 0x63, 0x75, 0x69, 0x2f, 0x79, 0x61, 0x74, 0x6d, 0x2f,
|
||||
0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x9f, 0x01, 0x0a, 0x10, 0x4a, 0x6f, 0x62, 0x44,
|
||||
0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x40, 0x0a, 0x07,
|
||||
0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e,
|
||||
0x6a, 0x6f, 0x62, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x41,
|
||||
0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x50, 0x61,
|
||||
0x72, 0x61, 0x6d, 0x48, 0x00, 0x52, 0x07, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x40,
|
||||
0x0a, 0x07, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x24, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x4a, 0x6f,
|
||||
0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68,
|
||||
0x50, 0x61, 0x72, 0x61, 0x6d, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65,
|
||||
0x42, 0x07, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x4f, 0x0a, 0x0c, 0x43, 0x72, 0x65,
|
||||
0x61, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69,
|
||||
0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x72, 0x69,
|
||||
0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x18, 0x11,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6a, 0x6f, 0x62, 0x2e, 0x4a, 0x6f, 0x62, 0x50, 0x61,
|
||||
0x72, 0x61, 0x6d, 0x52, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x90, 0x01, 0x0a, 0x09, 0x4a,
|
||||
0x6f, 0x62, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x6a, 0x6f, 0x62, 0x2e, 0x4a,
|
||||
0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x21,
|
||||
0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01, 0x01,
|
||||
0x12, 0x1b, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x22, 0x20, 0x01, 0x28, 0x03,
|
||||
0x48, 0x02, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a,
|
||||
0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d,
|
||||
0x69, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x7f, 0x0a,
|
||||
0x17, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x0f, 0x75, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x5f, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x03, 0x48, 0x00, 0x52, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6e, 0x63, 0x65,
|
||||
0x4e, 0x73, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x21,
|
||||
0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01, 0x01,
|
||||
0x42, 0x12, 0x0a, 0x10, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x69, 0x6e, 0x63,
|
||||
0x65, 0x5f, 0x6e, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x8f,
|
||||
0x01, 0x0a, 0x0a, 0x4a, 0x6f, 0x62, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x3a, 0x0a,
|
||||
0x07, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e,
|
||||
0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x2e, 0x4a, 0x6f, 0x62,
|
||||
0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x48, 0x00,
|
||||
0x52, 0x07, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x72, 0x65, 0x73,
|
||||
0x74, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6a, 0x6f, 0x62,
|
||||
0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74,
|
||||
0x6f, 0x72, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79,
|
||||
0x2a, 0x6b, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x09, 0x0a,
|
||||
0x05, 0x44, 0x52, 0x41, 0x46, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x54, 0x5f,
|
||||
0x52, 0x45, 0x41, 0x44, 0x59, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49,
|
||||
0x4e, 0x47, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, 0x49,
|
||||
0x4e, 0x47, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45,
|
||||
0x44, 0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x7f, 0x12,
|
||||
0x0c, 0x0a, 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0xff, 0x01, 0x42, 0x23, 0x5a,
|
||||
0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x61, 0x6d, 0x75,
|
||||
0x65, 0x6c, 0x6e, 0x63, 0x75, 0x69, 0x2f, 0x79, 0x61, 0x74, 0x6d, 0x2f, 0x65, 0x6e, 0x74, 0x69,
|
||||
0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -769,7 +770,7 @@ var file_job_proto_goTypes = []interface{}{
|
||||
(*Job)(nil), // 1: job.Job
|
||||
(*JobParam)(nil), // 2: job.JobParam
|
||||
(*JobState)(nil), // 3: job.JobState
|
||||
(*JobNextParam)(nil), // 4: job.JobNextParam
|
||||
(*JobDispatchParam)(nil), // 4: job.JobDispatchParam
|
||||
(*CreatableJob)(nil), // 5: job.CreatableJob
|
||||
(*JobFilter)(nil), // 6: job.JobFilter
|
||||
(*JobRecentlyUpdateFilter)(nil), // 7: job.JobRecentlyUpdateFilter
|
||||
@@ -778,8 +779,8 @@ var file_job_proto_goTypes = []interface{}{
|
||||
(*JobRestoreParam)(nil), // 10: job_restore.JobRestoreParam
|
||||
(*JobArchiveState)(nil), // 11: job_archive.JobArchiveState
|
||||
(*JobRestoreState)(nil), // 12: job_restore.JobRestoreState
|
||||
(*JobArchiveNextParam)(nil), // 13: job_archive.JobArchiveNextParam
|
||||
(*JobRestoreNextParam)(nil), // 14: job_restore.JobRestoreNextParam
|
||||
(*JobArchiveDispatchParam)(nil), // 13: job_archive.JobArchiveDispatchParam
|
||||
(*JobRestoreDispatchParam)(nil), // 14: job_restore.JobRestoreDispatchParam
|
||||
(*JobArchiveDisplay)(nil), // 15: job_archive.JobArchiveDisplay
|
||||
(*JobRestoreDisplay)(nil), // 16: job_restore.JobRestoreDisplay
|
||||
}
|
||||
@@ -790,8 +791,8 @@ var file_job_proto_depIdxs = []int32{
|
||||
10, // 3: job.JobParam.restore:type_name -> job_restore.JobRestoreParam
|
||||
11, // 4: job.JobState.archive:type_name -> job_archive.JobArchiveState
|
||||
12, // 5: job.JobState.restore:type_name -> job_restore.JobRestoreState
|
||||
13, // 6: job.JobNextParam.archive:type_name -> job_archive.JobArchiveNextParam
|
||||
14, // 7: job.JobNextParam.restore:type_name -> job_restore.JobRestoreNextParam
|
||||
13, // 6: job.JobDispatchParam.archive:type_name -> job_archive.JobArchiveDispatchParam
|
||||
14, // 7: job.JobDispatchParam.restore:type_name -> job_restore.JobRestoreDispatchParam
|
||||
2, // 8: job.CreatableJob.param:type_name -> job.JobParam
|
||||
0, // 9: job.JobFilter.status:type_name -> job.JobStatus
|
||||
15, // 10: job.JobDisplay.archive:type_name -> job_archive.JobArchiveDisplay
|
||||
@@ -848,7 +849,7 @@ func file_job_proto_init() {
|
||||
}
|
||||
}
|
||||
file_job_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*JobNextParam); i {
|
||||
switch v := v.(*JobDispatchParam); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -917,8 +918,8 @@ func file_job_proto_init() {
|
||||
(*JobState_Restore)(nil),
|
||||
}
|
||||
file_job_proto_msgTypes[3].OneofWrappers = []interface{}{
|
||||
(*JobNextParam_Archive)(nil),
|
||||
(*JobNextParam_Restore)(nil),
|
||||
(*JobDispatchParam_Archive)(nil),
|
||||
(*JobDispatchParam_Restore)(nil),
|
||||
}
|
||||
file_job_proto_msgTypes[5].OneofWrappers = []interface{}{}
|
||||
file_job_proto_msgTypes[6].OneofWrappers = []interface{}{}
|
||||
|
||||
+3
-3
@@ -41,10 +41,10 @@ message JobState {
|
||||
}
|
||||
}
|
||||
|
||||
message JobNextParam {
|
||||
message JobDispatchParam {
|
||||
oneof param {
|
||||
job_archive.JobArchiveNextParam archive = 1;
|
||||
job_restore.JobRestoreNextParam restore = 2;
|
||||
job_archive.JobArchiveDispatchParam archive = 1;
|
||||
job_restore.JobRestoreDispatchParam restore = 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+88
-88
@@ -119,20 +119,20 @@ func (x *JobArchiveParam) GetSources() []*Source {
|
||||
return nil
|
||||
}
|
||||
|
||||
type JobArchiveNextParam struct {
|
||||
type JobArchiveDispatchParam struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Param:
|
||||
// *JobArchiveNextParam_WaitForTape
|
||||
// *JobArchiveNextParam_Copying
|
||||
// *JobArchiveNextParam_Finished
|
||||
Param isJobArchiveNextParam_Param `protobuf_oneof:"param"`
|
||||
// *JobArchiveDispatchParam_WaitForTape
|
||||
// *JobArchiveDispatchParam_Copying
|
||||
// *JobArchiveDispatchParam_Finished
|
||||
Param isJobArchiveDispatchParam_Param `protobuf_oneof:"param"`
|
||||
}
|
||||
|
||||
func (x *JobArchiveNextParam) Reset() {
|
||||
*x = JobArchiveNextParam{}
|
||||
func (x *JobArchiveDispatchParam) Reset() {
|
||||
*x = JobArchiveDispatchParam{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_job_archive_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -140,13 +140,13 @@ func (x *JobArchiveNextParam) Reset() {
|
||||
}
|
||||
}
|
||||
|
||||
func (x *JobArchiveNextParam) String() string {
|
||||
func (x *JobArchiveDispatchParam) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*JobArchiveNextParam) ProtoMessage() {}
|
||||
func (*JobArchiveDispatchParam) ProtoMessage() {}
|
||||
|
||||
func (x *JobArchiveNextParam) ProtoReflect() protoreflect.Message {
|
||||
func (x *JobArchiveDispatchParam) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_job_archive_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -158,60 +158,60 @@ func (x *JobArchiveNextParam) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use JobArchiveNextParam.ProtoReflect.Descriptor instead.
|
||||
func (*JobArchiveNextParam) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use JobArchiveDispatchParam.ProtoReflect.Descriptor instead.
|
||||
func (*JobArchiveDispatchParam) Descriptor() ([]byte, []int) {
|
||||
return file_job_archive_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (m *JobArchiveNextParam) GetParam() isJobArchiveNextParam_Param {
|
||||
func (m *JobArchiveDispatchParam) GetParam() isJobArchiveDispatchParam_Param {
|
||||
if m != nil {
|
||||
return m.Param
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *JobArchiveNextParam) GetWaitForTape() *JobArchiveWaitForTapeParam {
|
||||
if x, ok := x.GetParam().(*JobArchiveNextParam_WaitForTape); ok {
|
||||
func (x *JobArchiveDispatchParam) GetWaitForTape() *JobArchiveWaitForTapeParam {
|
||||
if x, ok := x.GetParam().(*JobArchiveDispatchParam_WaitForTape); ok {
|
||||
return x.WaitForTape
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *JobArchiveNextParam) GetCopying() *JobArchiveCopyingParam {
|
||||
if x, ok := x.GetParam().(*JobArchiveNextParam_Copying); ok {
|
||||
func (x *JobArchiveDispatchParam) GetCopying() *JobArchiveCopyingParam {
|
||||
if x, ok := x.GetParam().(*JobArchiveDispatchParam_Copying); ok {
|
||||
return x.Copying
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *JobArchiveNextParam) GetFinished() *JobArchiveFinishedParam {
|
||||
if x, ok := x.GetParam().(*JobArchiveNextParam_Finished); ok {
|
||||
func (x *JobArchiveDispatchParam) GetFinished() *JobArchiveFinishedParam {
|
||||
if x, ok := x.GetParam().(*JobArchiveDispatchParam_Finished); ok {
|
||||
return x.Finished
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isJobArchiveNextParam_Param interface {
|
||||
isJobArchiveNextParam_Param()
|
||||
type isJobArchiveDispatchParam_Param interface {
|
||||
isJobArchiveDispatchParam_Param()
|
||||
}
|
||||
|
||||
type JobArchiveNextParam_WaitForTape struct {
|
||||
type JobArchiveDispatchParam_WaitForTape struct {
|
||||
WaitForTape *JobArchiveWaitForTapeParam `protobuf:"bytes,1,opt,name=wait_for_tape,json=waitForTape,proto3,oneof"`
|
||||
}
|
||||
|
||||
type JobArchiveNextParam_Copying struct {
|
||||
type JobArchiveDispatchParam_Copying struct {
|
||||
Copying *JobArchiveCopyingParam `protobuf:"bytes,2,opt,name=copying,proto3,oneof"`
|
||||
}
|
||||
|
||||
type JobArchiveNextParam_Finished struct {
|
||||
type JobArchiveDispatchParam_Finished struct {
|
||||
Finished *JobArchiveFinishedParam `protobuf:"bytes,255,opt,name=finished,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*JobArchiveNextParam_WaitForTape) isJobArchiveNextParam_Param() {}
|
||||
func (*JobArchiveDispatchParam_WaitForTape) isJobArchiveDispatchParam_Param() {}
|
||||
|
||||
func (*JobArchiveNextParam_Copying) isJobArchiveNextParam_Param() {}
|
||||
func (*JobArchiveDispatchParam_Copying) isJobArchiveDispatchParam_Param() {}
|
||||
|
||||
func (*JobArchiveNextParam_Finished) isJobArchiveNextParam_Param() {}
|
||||
func (*JobArchiveDispatchParam_Finished) isJobArchiveDispatchParam_Param() {}
|
||||
|
||||
type JobArchiveWaitForTapeParam struct {
|
||||
state protoimpl.MessageState
|
||||
@@ -503,61 +503,61 @@ var file_job_archive_proto_rawDesc = []byte{
|
||||
0x0a, 0x0f, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x50, 0x61, 0x72, 0x61,
|
||||
0x6d, 0x12, 0x28, 0x0a, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x6f, 0x75, 0x72,
|
||||
0x63, 0x65, 0x52, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22, 0xf3, 0x01, 0x0a, 0x13,
|
||||
0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x4e, 0x65, 0x78, 0x74, 0x50, 0x61,
|
||||
0x72, 0x61, 0x6d, 0x12, 0x4d, 0x0a, 0x0d, 0x77, 0x61, 0x69, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x5f,
|
||||
0x74, 0x61, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6a, 0x6f, 0x62,
|
||||
0x63, 0x65, 0x52, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22, 0xf7, 0x01, 0x0a, 0x17,
|
||||
0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74,
|
||||
0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x4d, 0x0a, 0x0d, 0x77, 0x61, 0x69, 0x74, 0x5f,
|
||||
0x66, 0x6f, 0x72, 0x5f, 0x74, 0x61, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,
|
||||
0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x2e, 0x4a, 0x6f, 0x62,
|
||||
0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x57, 0x61, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x54, 0x61,
|
||||
0x70, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x48, 0x00, 0x52, 0x0b, 0x77, 0x61, 0x69, 0x74, 0x46,
|
||||
0x6f, 0x72, 0x54, 0x61, 0x70, 0x65, 0x12, 0x3f, 0x0a, 0x07, 0x63, 0x6f, 0x70, 0x79, 0x69, 0x6e,
|
||||
0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x61, 0x72,
|
||||
0x63, 0x68, 0x69, 0x76, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65,
|
||||
0x43, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x48, 0x00, 0x52, 0x07,
|
||||
0x63, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x12, 0x43, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73,
|
||||
0x68, 0x65, 0x64, 0x18, 0xff, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6a, 0x6f, 0x62,
|
||||
0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68,
|
||||
0x69, 0x76, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d,
|
||||
0x48, 0x00, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05,
|
||||
0x70, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x1c, 0x0a, 0x1a, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68,
|
||||
0x69, 0x76, 0x65, 0x57, 0x61, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x54, 0x61, 0x70, 0x65, 0x50, 0x61,
|
||||
0x72, 0x61, 0x6d, 0x48, 0x00, 0x52, 0x0b, 0x77, 0x61, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x54, 0x61,
|
||||
0x70, 0x65, 0x12, 0x3f, 0x0a, 0x07, 0x63, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76,
|
||||
0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x70, 0x79,
|
||||
0x69, 0x6e, 0x67, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x70, 0x79,
|
||||
0x69, 0x6e, 0x67, 0x12, 0x43, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18,
|
||||
0xff, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x61, 0x72, 0x63,
|
||||
0x68, 0x69, 0x76, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x46,
|
||||
0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x48, 0x00, 0x52, 0x08,
|
||||
0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x61,
|
||||
0x6d, 0x22, 0x1c, 0x0a, 0x1a, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x57,
|
||||
0x61, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x54, 0x61, 0x70, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x22,
|
||||
0x5e, 0x0a, 0x16, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x70,
|
||||
0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x76,
|
||||
0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63,
|
||||
0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22,
|
||||
0x19, 0x0a, 0x17, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x46, 0x69, 0x6e,
|
||||
0x69, 0x73, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x71, 0x0a, 0x0f, 0x4a, 0x6f,
|
||||
0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2f, 0x0a,
|
||||
0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6a, 0x6f,
|
||||
0x62, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63,
|
||||
0x68, 0x69, 0x76, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x12, 0x2d,
|
||||
0x0a, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x13, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53,
|
||||
0x74, 0x61, 0x74, 0x65, 0x52, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22, 0xdf, 0x01,
|
||||
0x0a, 0x11, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70,
|
||||
0x6c, 0x61, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x62, 0x79,
|
||||
0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x6f, 0x70, 0x69, 0x65,
|
||||
0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64,
|
||||
0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x6f,
|
||||
0x70, 0x69, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74,
|
||||
0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a,
|
||||
0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f,
|
||||
0x74, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||
0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x05, 0x73,
|
||||
0x70, 0x65, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x05, 0x73, 0x70,
|
||||
0x65, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72,
|
||||
0x74, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x2a,
|
||||
0x4c, 0x0a, 0x0e, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x53, 0x74, 0x65,
|
||||
0x70, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x11,
|
||||
0x0a, 0x0d, 0x57, 0x41, 0x49, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x54, 0x41, 0x50, 0x45, 0x10,
|
||||
0x01, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0d,
|
||||
0x0a, 0x08, 0x46, 0x49, 0x4e, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0xff, 0x01, 0x42, 0x23, 0x5a,
|
||||
0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x61, 0x6d, 0x75,
|
||||
0x65, 0x6c, 0x6e, 0x63, 0x75, 0x69, 0x2f, 0x79, 0x61, 0x74, 0x6d, 0x2f, 0x65, 0x6e, 0x74, 0x69,
|
||||
0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x72, 0x61, 0x6d, 0x22, 0x5e, 0x0a, 0x16, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76,
|
||||
0x65, 0x43, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64,
|
||||
0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x72, 0x63, 0x6f, 0x64, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76,
|
||||
0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x71,
|
||||
0x0a, 0x0f, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x53, 0x74, 0x61, 0x74,
|
||||
0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x1b, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x2e, 0x4a, 0x6f,
|
||||
0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x04, 0x73, 0x74,
|
||||
0x65, 0x70, 0x12, 0x2d, 0x0a, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x6f, 0x75,
|
||||
0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
||||
0x73, 0x22, 0xdf, 0x01, 0x0a, 0x11, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65,
|
||||
0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x70, 0x69, 0x65,
|
||||
0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63,
|
||||
0x6f, 0x70, 0x69, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f,
|
||||
0x70, 0x69, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x0b, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1f, 0x0a,
|
||||
0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1f,
|
||||
0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12,
|
||||
0x19, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00,
|
||||
0x52, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74,
|
||||
0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09,
|
||||
0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x70,
|
||||
0x65, 0x65, 0x64, 0x2a, 0x4c, 0x0a, 0x0e, 0x4a, 0x6f, 0x62, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76,
|
||||
0x65, 0x53, 0x74, 0x65, 0x70, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47,
|
||||
0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x57, 0x41, 0x49, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x54,
|
||||
0x41, 0x50, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47,
|
||||
0x10, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x49, 0x4e, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0xff,
|
||||
0x01, 0x42, 0x23, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
||||
0x73, 0x61, 0x6d, 0x75, 0x65, 0x6c, 0x6e, 0x63, 0x75, 0x69, 0x2f, 0x79, 0x61, 0x74, 0x6d, 0x2f,
|
||||
0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -577,7 +577,7 @@ var file_job_archive_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
var file_job_archive_proto_goTypes = []interface{}{
|
||||
(JobArchiveStep)(0), // 0: job_archive.JobArchiveStep
|
||||
(*JobArchiveParam)(nil), // 1: job_archive.JobArchiveParam
|
||||
(*JobArchiveNextParam)(nil), // 2: job_archive.JobArchiveNextParam
|
||||
(*JobArchiveDispatchParam)(nil), // 2: job_archive.JobArchiveDispatchParam
|
||||
(*JobArchiveWaitForTapeParam)(nil), // 3: job_archive.JobArchiveWaitForTapeParam
|
||||
(*JobArchiveCopyingParam)(nil), // 4: job_archive.JobArchiveCopyingParam
|
||||
(*JobArchiveFinishedParam)(nil), // 5: job_archive.JobArchiveFinishedParam
|
||||
@@ -588,9 +588,9 @@ var file_job_archive_proto_goTypes = []interface{}{
|
||||
}
|
||||
var file_job_archive_proto_depIdxs = []int32{
|
||||
8, // 0: job_archive.JobArchiveParam.sources:type_name -> source.Source
|
||||
3, // 1: job_archive.JobArchiveNextParam.wait_for_tape:type_name -> job_archive.JobArchiveWaitForTapeParam
|
||||
4, // 2: job_archive.JobArchiveNextParam.copying:type_name -> job_archive.JobArchiveCopyingParam
|
||||
5, // 3: job_archive.JobArchiveNextParam.finished:type_name -> job_archive.JobArchiveFinishedParam
|
||||
3, // 1: job_archive.JobArchiveDispatchParam.wait_for_tape:type_name -> job_archive.JobArchiveWaitForTapeParam
|
||||
4, // 2: job_archive.JobArchiveDispatchParam.copying:type_name -> job_archive.JobArchiveCopyingParam
|
||||
5, // 3: job_archive.JobArchiveDispatchParam.finished:type_name -> job_archive.JobArchiveFinishedParam
|
||||
0, // 4: job_archive.JobArchiveState.step:type_name -> job_archive.JobArchiveStep
|
||||
9, // 5: job_archive.JobArchiveState.sources:type_name -> source.SourceState
|
||||
6, // [6:6] is the sub-list for method output_type
|
||||
@@ -620,7 +620,7 @@ func file_job_archive_proto_init() {
|
||||
}
|
||||
}
|
||||
file_job_archive_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*JobArchiveNextParam); i {
|
||||
switch v := v.(*JobArchiveDispatchParam); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -693,9 +693,9 @@ func file_job_archive_proto_init() {
|
||||
}
|
||||
}
|
||||
file_job_archive_proto_msgTypes[1].OneofWrappers = []interface{}{
|
||||
(*JobArchiveNextParam_WaitForTape)(nil),
|
||||
(*JobArchiveNextParam_Copying)(nil),
|
||||
(*JobArchiveNextParam_Finished)(nil),
|
||||
(*JobArchiveDispatchParam_WaitForTape)(nil),
|
||||
(*JobArchiveDispatchParam_Copying)(nil),
|
||||
(*JobArchiveDispatchParam_Finished)(nil),
|
||||
}
|
||||
file_job_archive_proto_msgTypes[6].OneofWrappers = []interface{}{}
|
||||
type x struct{}
|
||||
|
||||
@@ -16,7 +16,7 @@ message JobArchiveParam {
|
||||
repeated source.Source sources = 1;
|
||||
}
|
||||
|
||||
message JobArchiveNextParam {
|
||||
message JobArchiveDispatchParam {
|
||||
oneof param {
|
||||
JobArchiveWaitForTapeParam wait_for_tape = 1;
|
||||
JobArchiveCopyingParam copying = 2;
|
||||
|
||||
+111
-111
@@ -119,20 +119,20 @@ func (x *JobRestoreParam) GetFileIds() []int64 {
|
||||
return nil
|
||||
}
|
||||
|
||||
type JobRestoreNextParam struct {
|
||||
type JobRestoreDispatchParam struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Param:
|
||||
// *JobRestoreNextParam_WaitForTape
|
||||
// *JobRestoreNextParam_Copying
|
||||
// *JobRestoreNextParam_Finished
|
||||
Param isJobRestoreNextParam_Param `protobuf_oneof:"param"`
|
||||
// *JobRestoreDispatchParam_WaitForTape
|
||||
// *JobRestoreDispatchParam_Copying
|
||||
// *JobRestoreDispatchParam_Finished
|
||||
Param isJobRestoreDispatchParam_Param `protobuf_oneof:"param"`
|
||||
}
|
||||
|
||||
func (x *JobRestoreNextParam) Reset() {
|
||||
*x = JobRestoreNextParam{}
|
||||
func (x *JobRestoreDispatchParam) Reset() {
|
||||
*x = JobRestoreDispatchParam{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_job_restore_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -140,13 +140,13 @@ func (x *JobRestoreNextParam) Reset() {
|
||||
}
|
||||
}
|
||||
|
||||
func (x *JobRestoreNextParam) String() string {
|
||||
func (x *JobRestoreDispatchParam) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*JobRestoreNextParam) ProtoMessage() {}
|
||||
func (*JobRestoreDispatchParam) ProtoMessage() {}
|
||||
|
||||
func (x *JobRestoreNextParam) ProtoReflect() protoreflect.Message {
|
||||
func (x *JobRestoreDispatchParam) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_job_restore_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -158,60 +158,60 @@ func (x *JobRestoreNextParam) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use JobRestoreNextParam.ProtoReflect.Descriptor instead.
|
||||
func (*JobRestoreNextParam) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use JobRestoreDispatchParam.ProtoReflect.Descriptor instead.
|
||||
func (*JobRestoreDispatchParam) Descriptor() ([]byte, []int) {
|
||||
return file_job_restore_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (m *JobRestoreNextParam) GetParam() isJobRestoreNextParam_Param {
|
||||
func (m *JobRestoreDispatchParam) GetParam() isJobRestoreDispatchParam_Param {
|
||||
if m != nil {
|
||||
return m.Param
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *JobRestoreNextParam) GetWaitForTape() *JobRestoreWaitForTapeParam {
|
||||
if x, ok := x.GetParam().(*JobRestoreNextParam_WaitForTape); ok {
|
||||
func (x *JobRestoreDispatchParam) GetWaitForTape() *JobRestoreWaitForTapeParam {
|
||||
if x, ok := x.GetParam().(*JobRestoreDispatchParam_WaitForTape); ok {
|
||||
return x.WaitForTape
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *JobRestoreNextParam) GetCopying() *JobRestoreCopyingParam {
|
||||
if x, ok := x.GetParam().(*JobRestoreNextParam_Copying); ok {
|
||||
func (x *JobRestoreDispatchParam) GetCopying() *JobRestoreCopyingParam {
|
||||
if x, ok := x.GetParam().(*JobRestoreDispatchParam_Copying); ok {
|
||||
return x.Copying
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *JobRestoreNextParam) GetFinished() *JobRestoreFinishedParam {
|
||||
if x, ok := x.GetParam().(*JobRestoreNextParam_Finished); ok {
|
||||
func (x *JobRestoreDispatchParam) GetFinished() *JobRestoreFinishedParam {
|
||||
if x, ok := x.GetParam().(*JobRestoreDispatchParam_Finished); ok {
|
||||
return x.Finished
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isJobRestoreNextParam_Param interface {
|
||||
isJobRestoreNextParam_Param()
|
||||
type isJobRestoreDispatchParam_Param interface {
|
||||
isJobRestoreDispatchParam_Param()
|
||||
}
|
||||
|
||||
type JobRestoreNextParam_WaitForTape struct {
|
||||
type JobRestoreDispatchParam_WaitForTape struct {
|
||||
WaitForTape *JobRestoreWaitForTapeParam `protobuf:"bytes,1,opt,name=wait_for_tape,json=waitForTape,proto3,oneof"`
|
||||
}
|
||||
|
||||
type JobRestoreNextParam_Copying struct {
|
||||
type JobRestoreDispatchParam_Copying struct {
|
||||
Copying *JobRestoreCopyingParam `protobuf:"bytes,2,opt,name=copying,proto3,oneof"`
|
||||
}
|
||||
|
||||
type JobRestoreNextParam_Finished struct {
|
||||
type JobRestoreDispatchParam_Finished struct {
|
||||
Finished *JobRestoreFinishedParam `protobuf:"bytes,255,opt,name=finished,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*JobRestoreNextParam_WaitForTape) isJobRestoreNextParam_Param() {}
|
||||
func (*JobRestoreDispatchParam_WaitForTape) isJobRestoreDispatchParam_Param() {}
|
||||
|
||||
func (*JobRestoreNextParam_Copying) isJobRestoreNextParam_Param() {}
|
||||
func (*JobRestoreDispatchParam_Copying) isJobRestoreDispatchParam_Param() {}
|
||||
|
||||
func (*JobRestoreNextParam_Finished) isJobRestoreNextParam_Param() {}
|
||||
func (*JobRestoreDispatchParam_Finished) isJobRestoreDispatchParam_Param() {}
|
||||
|
||||
type JobRestoreWaitForTapeParam struct {
|
||||
state protoimpl.MessageState
|
||||
@@ -661,84 +661,84 @@ var file_job_restore_proto_rawDesc = []byte{
|
||||
0x6f, 0x74, 0x6f, 0x22, 0x2c, 0x0a, 0x0f, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72,
|
||||
0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69,
|
||||
0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64,
|
||||
0x73, 0x22, 0xf3, 0x01, 0x0a, 0x13, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65,
|
||||
0x4e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x4d, 0x0a, 0x0d, 0x77, 0x61, 0x69,
|
||||
0x74, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x74, 0x61, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x27, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x4a,
|
||||
0x73, 0x22, 0xf7, 0x01, 0x0a, 0x17, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65,
|
||||
0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x4d, 0x0a,
|
||||
0x0d, 0x77, 0x61, 0x69, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x74, 0x61, 0x70, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f,
|
||||
0x72, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x57, 0x61, 0x69,
|
||||
0x74, 0x46, 0x6f, 0x72, 0x54, 0x61, 0x70, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x48, 0x00, 0x52,
|
||||
0x0b, 0x77, 0x61, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x54, 0x61, 0x70, 0x65, 0x12, 0x3f, 0x0a, 0x07,
|
||||
0x63, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e,
|
||||
0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x52,
|
||||
0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x72,
|
||||
0x61, 0x6d, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x12, 0x43, 0x0a,
|
||||
0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0xff, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x24, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x4a,
|
||||
0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65,
|
||||
0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x48, 0x00, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68,
|
||||
0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x1c, 0x0a, 0x1a, 0x4a,
|
||||
0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x57, 0x61, 0x69, 0x74, 0x46, 0x6f, 0x72,
|
||||
0x54, 0x61, 0x70, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x48, 0x00, 0x52, 0x0b, 0x77, 0x61, 0x69,
|
||||
0x74, 0x46, 0x6f, 0x72, 0x54, 0x61, 0x70, 0x65, 0x12, 0x3f, 0x0a, 0x07, 0x63, 0x6f, 0x70, 0x79,
|
||||
0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6a, 0x6f, 0x62, 0x5f,
|
||||
0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f,
|
||||
0x72, 0x65, 0x43, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x48, 0x00,
|
||||
0x52, 0x07, 0x63, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x12, 0x43, 0x0a, 0x08, 0x66, 0x69, 0x6e,
|
||||
0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0xff, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6a,
|
||||
0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x65,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72,
|
||||
0x61, 0x6d, 0x48, 0x00, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x07,
|
||||
0x0a, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x1c, 0x0a, 0x1a, 0x4a, 0x6f, 0x62, 0x52, 0x65,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x65, 0x57, 0x61, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x54, 0x61, 0x70, 0x65,
|
||||
0x50, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x30, 0x0a, 0x16, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74,
|
||||
0x6f, 0x72, 0x65, 0x43, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x12,
|
||||
0x16, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x4a, 0x6f, 0x62, 0x52, 0x65,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72,
|
||||
0x61, 0x6d, 0x22, 0xf7, 0x01, 0x0a, 0x0b, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x69,
|
||||
0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74,
|
||||
0x61, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x74, 0x61,
|
||||
0x70, 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x70, 0x6f, 0x73, 0x69, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
|
||||
0x11, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x73, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x2e, 0x43, 0x6f, 0x70, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06,
|
||||
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x12,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61,
|
||||
0x73, 0x68, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1b,
|
||||
0x0a, 0x09, 0x74, 0x61, 0x70, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x21, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x08, 0x74, 0x61, 0x70, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x74,
|
||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x22, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68, 0x22, 0xa1, 0x01, 0x0a,
|
||||
0x0b, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x54, 0x61, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07,
|
||||
0x74, 0x61, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x74,
|
||||
0x61, 0x70, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x72, 0x63, 0x6f, 0x64, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x12,
|
||||
0x2f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x17, 0x2e, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x43, 0x6f,
|
||||
0x70, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x12, 0x2e, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x18, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73,
|
||||
0x22, 0x72, 0x0a, 0x0f, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0e, 0x32, 0x1b, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e,
|
||||
0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x04,
|
||||
0x73, 0x74, 0x65, 0x70, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x61, 0x70, 0x65, 0x73, 0x18, 0x02, 0x20,
|
||||
0x54, 0x61, 0x70, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x22, 0x30, 0x0a, 0x16, 0x4a, 0x6f, 0x62,
|
||||
0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61,
|
||||
0x72, 0x61, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x4a,
|
||||
0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65,
|
||||
0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x22, 0xf7, 0x01, 0x0a, 0x0b, 0x52, 0x65, 0x73, 0x74, 0x6f,
|
||||
0x72, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x12,
|
||||
0x17, 0x0a, 0x07, 0x74, 0x61, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x06, 0x74, 0x61, 0x70, 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x6f, 0x73, 0x69,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x70,
|
||||
0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x70, 0x79,
|
||||
0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x43, 0x6f, 0x70, 0x79, 0x53, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69,
|
||||
0x7a, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61,
|
||||
0x73, 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x70, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18,
|
||||
0x21, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x70, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12,
|
||||
0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x22,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68,
|
||||
0x22, 0xa1, 0x01, 0x0a, 0x0b, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x54, 0x61, 0x70, 0x65,
|
||||
0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x06, 0x74, 0x61, 0x70, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x72,
|
||||
0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x72, 0x63,
|
||||
0x6f, 0x64, 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x11, 0x20,
|
||||
0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x2e, 0x43, 0x6f, 0x70, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x12, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72,
|
||||
0x65, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x54, 0x61, 0x70, 0x65, 0x52, 0x05, 0x74,
|
||||
0x61, 0x70, 0x65, 0x73, 0x22, 0xdf, 0x01, 0x0a, 0x11, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74,
|
||||
0x6f, 0x72, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f,
|
||||
0x70, 0x69, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x0b, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a,
|
||||
0x0c, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73,
|
||||
0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65,
|
||||
0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x69, 0x6c,
|
||||
0x65, 0x73, 0x12, 0x19, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28,
|
||||
0x03, 0x48, 0x00, 0x52, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a,
|
||||
0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x08, 0x0a, 0x06,
|
||||
0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x2a, 0x4c, 0x0a, 0x0e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73,
|
||||
0x74, 0x6f, 0x72, 0x65, 0x53, 0x74, 0x65, 0x70, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44,
|
||||
0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x57, 0x41, 0x49, 0x54, 0x5f, 0x46, 0x4f,
|
||||
0x52, 0x5f, 0x54, 0x41, 0x50, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x50, 0x59,
|
||||
0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x49, 0x4e, 0x49, 0x53, 0x48, 0x45,
|
||||
0x44, 0x10, 0xff, 0x01, 0x42, 0x23, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x73, 0x61, 0x6d, 0x75, 0x65, 0x6c, 0x6e, 0x63, 0x75, 0x69, 0x2f, 0x79, 0x61,
|
||||
0x74, 0x6d, 0x2f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
0x65, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66,
|
||||
0x69, 0x6c, 0x65, 0x73, 0x22, 0x72, 0x0a, 0x0f, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f,
|
||||
0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65, 0x73, 0x74,
|
||||
0x6f, 0x72, 0x65, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x74,
|
||||
0x65, 0x70, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x61, 0x70, 0x65,
|
||||
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x65,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x54, 0x61, 0x70,
|
||||
0x65, 0x52, 0x05, 0x74, 0x61, 0x70, 0x65, 0x73, 0x22, 0xdf, 0x01, 0x0a, 0x11, 0x4a, 0x6f, 0x62,
|
||||
0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x12, 0x21,
|
||||
0x0a, 0x0c, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65,
|
||||
0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65,
|
||||
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x46,
|
||||
0x69, 0x6c, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79,
|
||||
0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c,
|
||||
0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66,
|
||||
0x69, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61,
|
||||
0x6c, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18,
|
||||
0x05, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x88, 0x01,
|
||||
0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
|
||||
0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65,
|
||||
0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x2a, 0x4c, 0x0a, 0x0e, 0x4a, 0x6f,
|
||||
0x62, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x74, 0x65, 0x70, 0x12, 0x0b, 0x0a, 0x07,
|
||||
0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x57, 0x41, 0x49,
|
||||
0x54, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x54, 0x41, 0x50, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07,
|
||||
0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x49, 0x4e,
|
||||
0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0xff, 0x01, 0x42, 0x23, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68,
|
||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x61, 0x6d, 0x75, 0x65, 0x6c, 0x6e, 0x63, 0x75,
|
||||
0x69, 0x2f, 0x79, 0x61, 0x74, 0x6d, 0x2f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -758,7 +758,7 @@ var file_job_restore_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
||||
var file_job_restore_proto_goTypes = []interface{}{
|
||||
(JobRestoreStep)(0), // 0: job_restore.JobRestoreStep
|
||||
(*JobRestoreParam)(nil), // 1: job_restore.JobRestoreParam
|
||||
(*JobRestoreNextParam)(nil), // 2: job_restore.JobRestoreNextParam
|
||||
(*JobRestoreDispatchParam)(nil), // 2: job_restore.JobRestoreDispatchParam
|
||||
(*JobRestoreWaitForTapeParam)(nil), // 3: job_restore.JobRestoreWaitForTapeParam
|
||||
(*JobRestoreCopyingParam)(nil), // 4: job_restore.JobRestoreCopyingParam
|
||||
(*JobRestoreFinishedParam)(nil), // 5: job_restore.JobRestoreFinishedParam
|
||||
@@ -769,9 +769,9 @@ var file_job_restore_proto_goTypes = []interface{}{
|
||||
(CopyStatus)(0), // 10: copy_status.CopyStatus
|
||||
}
|
||||
var file_job_restore_proto_depIdxs = []int32{
|
||||
3, // 0: job_restore.JobRestoreNextParam.wait_for_tape:type_name -> job_restore.JobRestoreWaitForTapeParam
|
||||
4, // 1: job_restore.JobRestoreNextParam.copying:type_name -> job_restore.JobRestoreCopyingParam
|
||||
5, // 2: job_restore.JobRestoreNextParam.finished:type_name -> job_restore.JobRestoreFinishedParam
|
||||
3, // 0: job_restore.JobRestoreDispatchParam.wait_for_tape:type_name -> job_restore.JobRestoreWaitForTapeParam
|
||||
4, // 1: job_restore.JobRestoreDispatchParam.copying:type_name -> job_restore.JobRestoreCopyingParam
|
||||
5, // 2: job_restore.JobRestoreDispatchParam.finished:type_name -> job_restore.JobRestoreFinishedParam
|
||||
10, // 3: job_restore.RestoreFile.status:type_name -> copy_status.CopyStatus
|
||||
10, // 4: job_restore.RestoreTape.status:type_name -> copy_status.CopyStatus
|
||||
6, // 5: job_restore.RestoreTape.files:type_name -> job_restore.RestoreFile
|
||||
@@ -804,7 +804,7 @@ func file_job_restore_proto_init() {
|
||||
}
|
||||
}
|
||||
file_job_restore_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*JobRestoreNextParam); i {
|
||||
switch v := v.(*JobRestoreDispatchParam); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -901,9 +901,9 @@ func file_job_restore_proto_init() {
|
||||
}
|
||||
}
|
||||
file_job_restore_proto_msgTypes[1].OneofWrappers = []interface{}{
|
||||
(*JobRestoreNextParam_WaitForTape)(nil),
|
||||
(*JobRestoreNextParam_Copying)(nil),
|
||||
(*JobRestoreNextParam_Finished)(nil),
|
||||
(*JobRestoreDispatchParam_WaitForTape)(nil),
|
||||
(*JobRestoreDispatchParam_Copying)(nil),
|
||||
(*JobRestoreDispatchParam_Finished)(nil),
|
||||
}
|
||||
file_job_restore_proto_msgTypes[8].OneofWrappers = []interface{}{}
|
||||
type x struct{}
|
||||
|
||||
@@ -16,7 +16,7 @@ message JobRestoreParam {
|
||||
repeated int64 file_ids = 1;
|
||||
}
|
||||
|
||||
message JobRestoreNextParam {
|
||||
message JobRestoreDispatchParam {
|
||||
oneof param {
|
||||
JobRestoreWaitForTapeParam wait_for_tape = 1;
|
||||
JobRestoreCopyingParam copying = 2;
|
||||
|
||||
+462
-323
File diff suppressed because it is too large
Load Diff
+14
-5
@@ -22,8 +22,9 @@ service Service {
|
||||
|
||||
rpc JobList(JobListRequest) returns (JobListReply) {}
|
||||
rpc JobCreate(JobCreateRequest) returns (JobCreateReply) {}
|
||||
rpc JobEditState(JobEditStateRequest) returns (JobEditStateReply) {}
|
||||
rpc JobDelete(JobDeleteRequest) returns (JobDeleteReply) {}
|
||||
rpc JobNext(JobNextRequest) returns (JobNextReply) {}
|
||||
rpc JobDispatch(JobDispatchRequest) returns (JobDispatchReply) {}
|
||||
rpc JobDisplay(JobDisplayRequest) returns (JobDisplayReply) {}
|
||||
rpc JobGetLog(JobGetLogRequest) returns (JobGetLogReply) {}
|
||||
|
||||
@@ -133,6 +134,15 @@ message JobCreateReply {
|
||||
job.Job job = 1;
|
||||
}
|
||||
|
||||
message JobEditStateRequest {
|
||||
int64 id = 1;
|
||||
optional job.JobStatus status = 2;
|
||||
optional job.JobState state = 3;
|
||||
}
|
||||
|
||||
message JobEditStateReply {
|
||||
}
|
||||
|
||||
message JobDeleteRequest {
|
||||
repeated int64 ids = 1;
|
||||
}
|
||||
@@ -140,13 +150,12 @@ message JobDeleteRequest {
|
||||
message JobDeleteReply {
|
||||
}
|
||||
|
||||
message JobNextRequest {
|
||||
message JobDispatchRequest {
|
||||
int64 id = 1;
|
||||
job.JobNextParam param = 2;
|
||||
job.JobDispatchParam param = 2;
|
||||
}
|
||||
|
||||
message JobNextReply {
|
||||
job.Job job = 1;
|
||||
message JobDispatchReply {
|
||||
}
|
||||
|
||||
message JobDisplayRequest {
|
||||
|
||||
+50
-14
@@ -32,8 +32,9 @@ type ServiceClient interface {
|
||||
TapeGetPositions(ctx context.Context, in *TapeGetPositionsRequest, opts ...grpc.CallOption) (*TapeGetPositionsReply, error)
|
||||
JobList(ctx context.Context, in *JobListRequest, opts ...grpc.CallOption) (*JobListReply, error)
|
||||
JobCreate(ctx context.Context, in *JobCreateRequest, opts ...grpc.CallOption) (*JobCreateReply, error)
|
||||
JobEditState(ctx context.Context, in *JobEditStateRequest, opts ...grpc.CallOption) (*JobEditStateReply, error)
|
||||
JobDelete(ctx context.Context, in *JobDeleteRequest, opts ...grpc.CallOption) (*JobDeleteReply, error)
|
||||
JobNext(ctx context.Context, in *JobNextRequest, opts ...grpc.CallOption) (*JobNextReply, error)
|
||||
JobDispatch(ctx context.Context, in *JobDispatchRequest, opts ...grpc.CallOption) (*JobDispatchReply, error)
|
||||
JobDisplay(ctx context.Context, in *JobDisplayRequest, opts ...grpc.CallOption) (*JobDisplayReply, error)
|
||||
JobGetLog(ctx context.Context, in *JobGetLogRequest, opts ...grpc.CallOption) (*JobGetLogReply, error)
|
||||
SourceList(ctx context.Context, in *SourceListRequest, opts ...grpc.CallOption) (*SourceListReply, error)
|
||||
@@ -140,6 +141,15 @@ func (c *serviceClient) JobCreate(ctx context.Context, in *JobCreateRequest, opt
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *serviceClient) JobEditState(ctx context.Context, in *JobEditStateRequest, opts ...grpc.CallOption) (*JobEditStateReply, error) {
|
||||
out := new(JobEditStateReply)
|
||||
err := c.cc.Invoke(ctx, "/service.Service/JobEditState", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *serviceClient) JobDelete(ctx context.Context, in *JobDeleteRequest, opts ...grpc.CallOption) (*JobDeleteReply, error) {
|
||||
out := new(JobDeleteReply)
|
||||
err := c.cc.Invoke(ctx, "/service.Service/JobDelete", in, out, opts...)
|
||||
@@ -149,9 +159,9 @@ func (c *serviceClient) JobDelete(ctx context.Context, in *JobDeleteRequest, opt
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *serviceClient) JobNext(ctx context.Context, in *JobNextRequest, opts ...grpc.CallOption) (*JobNextReply, error) {
|
||||
out := new(JobNextReply)
|
||||
err := c.cc.Invoke(ctx, "/service.Service/JobNext", in, out, opts...)
|
||||
func (c *serviceClient) JobDispatch(ctx context.Context, in *JobDispatchRequest, opts ...grpc.CallOption) (*JobDispatchReply, error) {
|
||||
out := new(JobDispatchReply)
|
||||
err := c.cc.Invoke(ctx, "/service.Service/JobDispatch", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -226,8 +236,9 @@ type ServiceServer interface {
|
||||
TapeGetPositions(context.Context, *TapeGetPositionsRequest) (*TapeGetPositionsReply, error)
|
||||
JobList(context.Context, *JobListRequest) (*JobListReply, error)
|
||||
JobCreate(context.Context, *JobCreateRequest) (*JobCreateReply, error)
|
||||
JobEditState(context.Context, *JobEditStateRequest) (*JobEditStateReply, error)
|
||||
JobDelete(context.Context, *JobDeleteRequest) (*JobDeleteReply, error)
|
||||
JobNext(context.Context, *JobNextRequest) (*JobNextReply, error)
|
||||
JobDispatch(context.Context, *JobDispatchRequest) (*JobDispatchReply, error)
|
||||
JobDisplay(context.Context, *JobDisplayRequest) (*JobDisplayReply, error)
|
||||
JobGetLog(context.Context, *JobGetLogRequest) (*JobGetLogReply, error)
|
||||
SourceList(context.Context, *SourceListRequest) (*SourceListReply, error)
|
||||
@@ -271,11 +282,14 @@ func (UnimplementedServiceServer) JobList(context.Context, *JobListRequest) (*Jo
|
||||
func (UnimplementedServiceServer) JobCreate(context.Context, *JobCreateRequest) (*JobCreateReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method JobCreate not implemented")
|
||||
}
|
||||
func (UnimplementedServiceServer) JobEditState(context.Context, *JobEditStateRequest) (*JobEditStateReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method JobEditState not implemented")
|
||||
}
|
||||
func (UnimplementedServiceServer) JobDelete(context.Context, *JobDeleteRequest) (*JobDeleteReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method JobDelete not implemented")
|
||||
}
|
||||
func (UnimplementedServiceServer) JobNext(context.Context, *JobNextRequest) (*JobNextReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method JobNext not implemented")
|
||||
func (UnimplementedServiceServer) JobDispatch(context.Context, *JobDispatchRequest) (*JobDispatchReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method JobDispatch not implemented")
|
||||
}
|
||||
func (UnimplementedServiceServer) JobDisplay(context.Context, *JobDisplayRequest) (*JobDisplayReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method JobDisplay not implemented")
|
||||
@@ -488,6 +502,24 @@ func _Service_JobCreate_Handler(srv interface{}, ctx context.Context, dec func(i
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Service_JobEditState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(JobEditStateRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ServiceServer).JobEditState(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/service.Service/JobEditState",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ServiceServer).JobEditState(ctx, req.(*JobEditStateRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Service_JobDelete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(JobDeleteRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -506,20 +538,20 @@ func _Service_JobDelete_Handler(srv interface{}, ctx context.Context, dec func(i
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Service_JobNext_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(JobNextRequest)
|
||||
func _Service_JobDispatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(JobDispatchRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ServiceServer).JobNext(ctx, in)
|
||||
return srv.(ServiceServer).JobDispatch(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/service.Service/JobNext",
|
||||
FullMethod: "/service.Service/JobDispatch",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ServiceServer).JobNext(ctx, req.(*JobNextRequest))
|
||||
return srv.(ServiceServer).JobDispatch(ctx, req.(*JobDispatchRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
@@ -679,13 +711,17 @@ var Service_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "JobCreate",
|
||||
Handler: _Service_JobCreate_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "JobEditState",
|
||||
Handler: _Service_JobEditState_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "JobDelete",
|
||||
Handler: _Service_JobDelete_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "JobNext",
|
||||
Handler: _Service_JobNext_Handler,
|
||||
MethodName: "JobDispatch",
|
||||
Handler: _Service_JobDispatch_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "JobDisplay",
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package executor
|
||||
|
||||
import "sort"
|
||||
|
||||
func (e *Executor) ListAvailableDevices() []string {
|
||||
e.devicesLock.Lock()
|
||||
defer e.devicesLock.Unlock()
|
||||
|
||||
devices := e.availableDevices.ToSlice()
|
||||
sort.Slice(devices, func(i, j int) bool {
|
||||
return devices[i] < devices[j]
|
||||
})
|
||||
|
||||
return devices
|
||||
}
|
||||
|
||||
func (e *Executor) OccupyDevice(dev string) bool {
|
||||
e.devicesLock.Lock()
|
||||
defer e.devicesLock.Unlock()
|
||||
|
||||
if !e.availableDevices.Contains(dev) {
|
||||
return false
|
||||
}
|
||||
|
||||
e.availableDevices.Remove(dev)
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *Executor) ReleaseDevice(dev string) {
|
||||
e.devicesLock.Lock()
|
||||
defer e.devicesLock.Unlock()
|
||||
e.availableDevices.Add(dev)
|
||||
}
|
||||
+62
-79
@@ -3,12 +3,13 @@ package executor
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
"github.com/modern-go/reflect2"
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
"github.com/samuelncui/yatm/library"
|
||||
"github.com/samuelncui/yatm/tools"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -16,13 +17,14 @@ type Executor struct {
|
||||
db *gorm.DB
|
||||
lib *library.Library
|
||||
|
||||
devices []string
|
||||
|
||||
devicesLock sync.Mutex
|
||||
devices []string
|
||||
availableDevices mapset.Set[string]
|
||||
|
||||
paths Paths
|
||||
scripts Scripts
|
||||
|
||||
jobExecutors *tools.CacheOnce[int64, JobExecutor]
|
||||
}
|
||||
|
||||
type Paths struct {
|
||||
@@ -43,7 +45,7 @@ func New(
|
||||
db *gorm.DB, lib *library.Library,
|
||||
devices []string, paths Paths, scripts Scripts,
|
||||
) *Executor {
|
||||
return &Executor{
|
||||
e := &Executor{
|
||||
db: db,
|
||||
lib: lib,
|
||||
devices: devices,
|
||||
@@ -51,89 +53,76 @@ func New(
|
||||
paths: paths,
|
||||
scripts: scripts,
|
||||
}
|
||||
e.jobExecutors = tools.NewCacheOnce(e.newJobExecutor)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *Executor) AutoMigrate() error {
|
||||
return e.db.AutoMigrate(ModelJob)
|
||||
}
|
||||
|
||||
func (e *Executor) ListAvailableDevices() []string {
|
||||
e.devicesLock.Lock()
|
||||
defer e.devicesLock.Unlock()
|
||||
func (e *Executor) CreateJob(ctx context.Context, job *Job, param *entity.JobParam) (*Job, error) {
|
||||
job, err := e.SaveJob(ctx, job)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("save job fail, err= %w", err)
|
||||
}
|
||||
|
||||
devices := e.availableDevices.ToSlice()
|
||||
sort.Slice(devices, func(i, j int) bool {
|
||||
return devices[i] < devices[j]
|
||||
})
|
||||
typ, found := jobTypes[jobParamToTypes[reflect2.RTypeOf(param.GetParam())]]
|
||||
if !found || typ == nil {
|
||||
return nil, fmt.Errorf("job type unexpected, state_type= %T", param.GetParam())
|
||||
}
|
||||
|
||||
return devices
|
||||
executor, err := typ.GetExecutor(ctx, e, job)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get job executor fail, job_id= %d, %w", job.ID, err)
|
||||
}
|
||||
if err := executor.Initialize(ctx, param); err != nil {
|
||||
executor.Logger().WithContext(ctx).WithError(err).Errorf("initialize failed, param= %s", param)
|
||||
return nil, fmt.Errorf("executor initialize fail, job_id= %d param= %s, %w", job.ID, param, err)
|
||||
}
|
||||
if err := executor.Close(ctx); err != nil {
|
||||
executor.Logger().WithContext(ctx).WithError(err).Errorf("close executor failed, param= %s", param)
|
||||
return nil, fmt.Errorf("close executor failed, job_id= %d param= %s, %w", job.ID, param, err)
|
||||
}
|
||||
|
||||
return job, nil
|
||||
}
|
||||
|
||||
func (e *Executor) occupyDevice(dev string) bool {
|
||||
e.devicesLock.Lock()
|
||||
defer e.devicesLock.Unlock()
|
||||
|
||||
if !e.availableDevices.Contains(dev) {
|
||||
return false
|
||||
}
|
||||
|
||||
e.availableDevices.Remove(dev)
|
||||
return true
|
||||
func (e *Executor) GetJobExecutor(ctx context.Context, id int64) (JobExecutor, error) {
|
||||
return e.jobExecutors.Get(ctx, id)
|
||||
}
|
||||
|
||||
func (e *Executor) releaseDevice(dev string) {
|
||||
e.devicesLock.Lock()
|
||||
defer e.devicesLock.Unlock()
|
||||
e.availableDevices.Add(dev)
|
||||
func (e *Executor) RemoveJobExecutor(ctx context.Context, id int64) {
|
||||
e.jobExecutors.Remove(id)
|
||||
}
|
||||
|
||||
func (e *Executor) Start(ctx context.Context, job *Job) error {
|
||||
job.Status = entity.JobStatus_PROCESSING
|
||||
if _, err := e.SaveJob(ctx, job); err != nil {
|
||||
return err
|
||||
func (e *Executor) newJobExecutor(ctx context.Context, id int64) (JobExecutor, error) {
|
||||
job, err := e.GetJob(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get job fail, id= %d, %w", id, err)
|
||||
}
|
||||
|
||||
if state := job.State.GetArchive(); state != nil {
|
||||
if err := e.startArchive(ctx, job); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if state := job.State.GetRestore(); state != nil {
|
||||
if err := e.startRestore(ctx, job); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
factory, has := jobTypes[reflect2.RTypeOf(job.State.GetState())]
|
||||
if !has {
|
||||
return nil, fmt.Errorf("job type unexpected, state_type= %T", job.State.GetState())
|
||||
}
|
||||
|
||||
return fmt.Errorf("unexpected state type, %T", job.State.State)
|
||||
return factory.GetExecutor(ctx, e, job)
|
||||
}
|
||||
|
||||
func (e *Executor) Submit(ctx context.Context, job *Job, param *entity.JobNextParam) error {
|
||||
if job.Status != entity.JobStatus_PROCESSING {
|
||||
return fmt.Errorf("target job is not on processing, status= %s", job.Status)
|
||||
func (e *Executor) Dispatch(ctx context.Context, jobID int64, param *entity.JobDispatchParam) error {
|
||||
executor, err := e.GetJobExecutor(ctx, jobID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get job executor fail, job_id= %d, %w", jobID, err)
|
||||
}
|
||||
|
||||
if state := job.State.GetArchive(); state != nil {
|
||||
exe, err := e.newArchiveExecutor(ctx, job)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exe.submit(ctx, param.GetArchive())
|
||||
return nil
|
||||
}
|
||||
if state := job.State.GetRestore(); state != nil {
|
||||
exe, err := e.newRestoreExecutor(ctx, job)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exe.submit(ctx, param.GetRestore())
|
||||
return nil
|
||||
if err := executor.Dispatch(ctx, param); err != nil {
|
||||
executor.Logger().WithContext(ctx).WithError(err).Errorf("dispatch request fail, req= %s", param)
|
||||
return fmt.Errorf("dispatch request fail, job_id= %d, req= %s, %w", jobID, param, err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unexpected state type, %T", job.State.State)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) Display(ctx context.Context, job *Job) (*entity.JobDisplay, error) {
|
||||
@@ -141,22 +130,16 @@ func (e *Executor) Display(ctx context.Context, job *Job) (*entity.JobDisplay, e
|
||||
return nil, fmt.Errorf("target job is not on processing, status= %s", job.Status)
|
||||
}
|
||||
|
||||
if state := job.State.GetArchive(); state != nil {
|
||||
display, err := e.getArchiveDisplay(ctx, job)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.JobDisplay{Display: &entity.JobDisplay_Archive{Archive: display}}, nil
|
||||
}
|
||||
if state := job.State.GetRestore(); state != nil {
|
||||
display, err := e.getRestoreDisplay(ctx, job)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.JobDisplay{Display: &entity.JobDisplay_Restore{Restore: display}}, nil
|
||||
executor, err := e.GetJobExecutor(ctx, job.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get job executor fail, job_id= %d, %w", job.ID, err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unexpected state type, %T", job.State.State)
|
||||
result, err := executor.Display(ctx)
|
||||
if err != nil {
|
||||
executor.Logger().WithContext(ctx).WithError(err).Errorf("get display failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -33,28 +33,6 @@ func (j *Job) BeforeUpdate(tx *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) initJob(ctx context.Context, job *Job, param *entity.JobParam) error {
|
||||
if p := param.GetArchive(); p != nil {
|
||||
return e.createArchive(ctx, job, p)
|
||||
}
|
||||
if p := param.GetRestore(); p != nil {
|
||||
return e.createRestore(ctx, job, p)
|
||||
}
|
||||
return fmt.Errorf("unexpected param type, %T", param.Param)
|
||||
}
|
||||
|
||||
func (e *Executor) CreateJob(ctx context.Context, job *Job, param *entity.JobParam) (*Job, error) {
|
||||
if err := e.initJob(ctx, job, param); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r := e.db.WithContext(ctx).Create(job); r.Error != nil {
|
||||
return nil, fmt.Errorf("save job fail, err= %w", r.Error)
|
||||
}
|
||||
|
||||
return job, nil
|
||||
}
|
||||
|
||||
func (e *Executor) DeleteJobs(ctx context.Context, ids ...int64) error {
|
||||
jobs, err := e.MGetJob(ctx, ids...)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package executor
|
||||
|
||||
type jobTypeArchive struct{}
|
||||
@@ -1,25 +0,0 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
)
|
||||
|
||||
func (e *Executor) getArchiveDisplay(ctx context.Context, job *Job) (*entity.JobArchiveDisplay, error) {
|
||||
display := new(entity.JobArchiveDisplay)
|
||||
|
||||
if exe := e.getArchiveExecutor(ctx, job); exe != nil && exe.progress != nil {
|
||||
display.CopiedBytes = atomic.LoadInt64(&exe.progress.bytes)
|
||||
display.CopiedFiles = atomic.LoadInt64(&exe.progress.files)
|
||||
display.TotalBytes = atomic.LoadInt64(&exe.progress.totalBytes)
|
||||
display.TotalFiles = atomic.LoadInt64(&exe.progress.totalFiles)
|
||||
display.StartTime = exe.progress.startTime.Unix()
|
||||
|
||||
speed := atomic.LoadInt64(&exe.progress.speed)
|
||||
display.Speed = &speed
|
||||
}
|
||||
|
||||
return display, nil
|
||||
}
|
||||
+78
-345
@@ -2,44 +2,30 @@ package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
"github.com/samber/lo"
|
||||
"github.com/samuelncui/acp"
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
"github.com/samuelncui/yatm/library"
|
||||
"github.com/samuelncui/yatm/tools"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
runningArchives sync.Map
|
||||
)
|
||||
type jobArchiveExecutor struct {
|
||||
lock sync.Mutex
|
||||
exe *Executor
|
||||
job *Job
|
||||
|
||||
func (e *Executor) getArchiveExecutor(ctx context.Context, job *Job) *jobArchiveExecutor {
|
||||
if running, has := runningArchives.Load(job.ID); has {
|
||||
return running.(*jobArchiveExecutor)
|
||||
}
|
||||
return nil
|
||||
progress *progress
|
||||
logFile *os.File
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
func (e *Executor) newArchiveExecutor(ctx context.Context, job *Job) (*jobArchiveExecutor, error) {
|
||||
if exe := e.getArchiveExecutor(ctx, job); exe != nil {
|
||||
return exe, nil
|
||||
}
|
||||
|
||||
logFile, err := e.newLogWriter(job.ID)
|
||||
func (*jobTypeArchive) GetExecutor(ctx context.Context, exe *Executor, job *Job) (JobExecutor, error) {
|
||||
logFile, err := exe.newLogWriter(job.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get log writer fail, %w", err)
|
||||
}
|
||||
@@ -47,39 +33,27 @@ func (e *Executor) newArchiveExecutor(ctx context.Context, job *Job) (*jobArchiv
|
||||
logger := logrus.New()
|
||||
logger.SetOutput(io.MultiWriter(os.Stderr, logFile))
|
||||
|
||||
exe := &jobArchiveExecutor{
|
||||
exe: e,
|
||||
e := &jobArchiveExecutor{
|
||||
exe: exe,
|
||||
job: job,
|
||||
|
||||
state: job.State.GetArchive(),
|
||||
|
||||
logFile: logFile,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
runningArchives.Store(job.ID, exe)
|
||||
return exe, nil
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type jobArchiveExecutor struct {
|
||||
exe *Executor
|
||||
job *Job
|
||||
|
||||
stateLock sync.Mutex
|
||||
state *entity.JobArchiveState
|
||||
|
||||
progress *progress
|
||||
logFile *os.File
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) submit(ctx context.Context, param *entity.JobArchiveNextParam) {
|
||||
if err := a.handle(ctx, param); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Infof("handler param fail, param= %s", param)
|
||||
func (a *jobArchiveExecutor) Dispatch(ctx context.Context, next *entity.JobDispatchParam) error {
|
||||
param := next.GetArchive()
|
||||
if param == nil {
|
||||
return fmt.Errorf("unexpected next param type, unexpected= JobArchiveDispatchParam, has= %s", next)
|
||||
}
|
||||
|
||||
return a.dispatch(ctx, param)
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) handle(ctx context.Context, param *entity.JobArchiveNextParam) error {
|
||||
func (a *jobArchiveExecutor) dispatch(ctx context.Context, param *entity.JobArchiveDispatchParam) error {
|
||||
if p := param.GetCopying(); p != nil {
|
||||
if err := a.switchStep(
|
||||
ctx, entity.JobArchiveStep_COPYING, entity.JobStatus_PROCESSING,
|
||||
@@ -91,6 +65,7 @@ func (a *jobArchiveExecutor) handle(ctx context.Context, param *entity.JobArchiv
|
||||
tools.Working()
|
||||
go tools.WrapWithLogger(ctx, a.logger, func() {
|
||||
defer tools.Done()
|
||||
|
||||
if err := a.makeTape(tools.ShutdownContext, p.Device, p.Barcode, p.Name); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("make tape has error, barcode= '%s' name= '%s'", p.Barcode, p.Name)
|
||||
}
|
||||
@@ -101,7 +76,7 @@ func (a *jobArchiveExecutor) handle(ctx context.Context, param *entity.JobArchiv
|
||||
|
||||
if p := param.GetWaitForTape(); p != nil {
|
||||
return a.switchStep(
|
||||
ctx, entity.JobArchiveStep_WAIT_FOR_TAPE, entity.JobStatus_PROCESSING,
|
||||
ctx, entity.JobArchiveStep_WAIT_FOR_TAPE, entity.JobStatus_PENDING,
|
||||
mapset.NewThreadUnsafeSet(entity.JobArchiveStep_PENDING, entity.JobArchiveStep_COPYING),
|
||||
)
|
||||
}
|
||||
@@ -114,322 +89,80 @@ func (a *jobArchiveExecutor) handle(ctx context.Context, param *entity.JobArchiv
|
||||
return err
|
||||
}
|
||||
|
||||
a.logFile.Close()
|
||||
runningArchives.Delete(a.job.ID)
|
||||
return nil
|
||||
return a.Close(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) makeTape(ctx context.Context, device, barcode, name string) (rerr error) {
|
||||
if !a.exe.occupyDevice(device) {
|
||||
return fmt.Errorf("device is using, device= %s", device)
|
||||
}
|
||||
defer a.exe.releaseDevice(device)
|
||||
defer a.makeTapeFinished(tools.WithoutTimeout(ctx))
|
||||
|
||||
encryption, keyPath, keyRecycle, err := a.exe.newKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer keyRecycle()
|
||||
|
||||
if err := runCmd(a.logger, a.exe.makeEncryptCmd(ctx, device, keyPath, barcode, name)); err != nil {
|
||||
return fmt.Errorf("run encrypt script fail, %w", err)
|
||||
func (a *jobArchiveExecutor) Display(ctx context.Context) (*entity.JobDisplay, error) {
|
||||
p := a.progress
|
||||
if p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mkfsCmd := exec.CommandContext(ctx, a.exe.scripts.Mkfs)
|
||||
mkfsCmd.Env = append(mkfsCmd.Env, fmt.Sprintf("DEVICE=%s", device), fmt.Sprintf("TAPE_BARCODE=%s", barcode), fmt.Sprintf("TAPE_NAME=%s", name))
|
||||
if err := runCmd(a.logger, mkfsCmd); err != nil {
|
||||
return fmt.Errorf("run mkfs script fail, %w", err)
|
||||
display := new(entity.JobArchiveDisplay)
|
||||
display.CopiedBytes = atomic.LoadInt64(&p.bytes)
|
||||
display.CopiedFiles = atomic.LoadInt64(&p.files)
|
||||
display.TotalBytes = atomic.LoadInt64(&p.totalBytes)
|
||||
display.TotalFiles = atomic.LoadInt64(&p.totalFiles)
|
||||
display.StartTime = p.startTime.Unix()
|
||||
|
||||
speed := atomic.LoadInt64(&p.speed)
|
||||
display.Speed = &speed
|
||||
|
||||
return &entity.JobDisplay{Display: &entity.JobDisplay_Archive{Archive: display}}, nil
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) Close(ctx context.Context) error {
|
||||
a.logFile.Close()
|
||||
a.exe.RemoveJobExecutor(ctx, a.job.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) Logger() *logrus.Logger {
|
||||
return a.logger
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) getState() *entity.JobArchiveState {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
if a.job.State == nil || a.job.State.GetArchive() == nil {
|
||||
a.job.State = &entity.JobState{State: &entity.JobState_Archive{Archive: &entity.JobArchiveState{}}}
|
||||
}
|
||||
|
||||
mountPoint, err := os.MkdirTemp("", "*.ltfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp mountpoint, %w", err)
|
||||
return a.job.State.GetArchive()
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) updateJob(ctx context.Context, change func(*Job, *entity.JobArchiveState) error) error {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
if a.job.State == nil || a.job.State.GetArchive() == nil {
|
||||
a.job.State = &entity.JobState{State: &entity.JobState_Archive{Archive: &entity.JobArchiveState{}}}
|
||||
}
|
||||
|
||||
mountCmd := exec.CommandContext(ctx, a.exe.scripts.Mount)
|
||||
mountCmd.Env = append(mountCmd.Env, fmt.Sprintf("DEVICE=%s", device), fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
if err := runCmd(a.logger, mountCmd); err != nil {
|
||||
return fmt.Errorf("run mount script fail, %w", err)
|
||||
if err := change(a.job, a.job.State.GetArchive()); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Warnf("update state failed while exec change callback")
|
||||
return fmt.Errorf("update state failed while exec change callback, %w", err)
|
||||
}
|
||||
defer func() {
|
||||
umountCmd := exec.CommandContext(tools.WithoutTimeout(ctx), a.exe.scripts.Umount)
|
||||
umountCmd.Env = append(umountCmd.Env, fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
if err := runCmd(a.logger, umountCmd); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("run umount script fail, %s", mountPoint)
|
||||
return
|
||||
}
|
||||
if err := os.Remove(mountPoint); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("remove mount point fail, %s", mountPoint)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
wildcardJobOpts := make([]acp.WildcardJobOption, 0, 6)
|
||||
wildcardJobOpts = append(wildcardJobOpts, acp.Target(mountPoint))
|
||||
for _, source := range a.state.Sources {
|
||||
if source.Status == entity.CopyStatus_SUBMITED {
|
||||
continue
|
||||
}
|
||||
wildcardJobOpts = append(wildcardJobOpts, acp.AccurateSource(source.Source.Base, source.Source.Path))
|
||||
if _, err := a.exe.SaveJob(ctx, a.job); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Warnf("update state failed while save job")
|
||||
return fmt.Errorf("update state failed while save job, %w", err)
|
||||
}
|
||||
|
||||
opts := make([]acp.Option, 0, 4)
|
||||
opts = append(opts, acp.WildcardJob(wildcardJobOpts...))
|
||||
opts = append(opts, acp.WithHash(true))
|
||||
opts = append(opts, acp.SetToDevice(acp.LinearDevice(true)))
|
||||
opts = append(opts, acp.WithLogger(a.logger))
|
||||
|
||||
reportHander, reportGetter := acp.NewReportGetter()
|
||||
opts = append(opts, acp.WithEventHandler(reportHander))
|
||||
|
||||
a.progress = newProgress()
|
||||
defer func() { a.progress = nil }()
|
||||
|
||||
var dropToReadonly bool
|
||||
opts = append(opts, acp.WithEventHandler(func(ev acp.Event) {
|
||||
switch e := ev.(type) {
|
||||
case *acp.EventUpdateCount:
|
||||
atomic.StoreInt64(&a.progress.totalBytes, e.Bytes)
|
||||
atomic.StoreInt64(&a.progress.totalFiles, e.Files)
|
||||
return
|
||||
case *acp.EventUpdateProgress:
|
||||
a.progress.setBytes(e.Bytes)
|
||||
atomic.StoreInt64(&a.progress.files, e.Files)
|
||||
return
|
||||
case *acp.EventReportError:
|
||||
a.logger.WithContext(ctx).Errorf("acp report error, src= '%s' dst= '%s' err= '%s'", e.Error.Src, e.Error.Dst, e.Error.Err)
|
||||
return
|
||||
case *acp.EventUpdateJob:
|
||||
job := e.Job
|
||||
src := entity.NewSourceFromACPJob(job)
|
||||
|
||||
var targetStatus entity.CopyStatus
|
||||
switch job.Status {
|
||||
case acp.JobStatusPending, acp.JobStatusPreparing:
|
||||
targetStatus = entity.CopyStatus_PENDING
|
||||
case acp.JobStatusCopying:
|
||||
targetStatus = entity.CopyStatus_RUNNING
|
||||
case acp.JobStatusFinished:
|
||||
targetStatus = entity.CopyStatus_FAILED
|
||||
if len(job.SuccessTargets) > 0 {
|
||||
a.logger.WithContext(ctx).Infof("file '%s' copy success, size= %d", src.RealPath(), job.Size)
|
||||
targetStatus = entity.CopyStatus_STAGED
|
||||
break // break from switch
|
||||
}
|
||||
|
||||
for dst, err := range job.FailTargets {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, acp.ErrTargetNoSpace) {
|
||||
continue
|
||||
}
|
||||
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("file '%s' copy fail, dst= '%s'", src.RealPath(), dst)
|
||||
if errors.Is(err, acp.ErrTargetDropToReadonly) {
|
||||
dropToReadonly = true
|
||||
}
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
a.stateLock.Lock()
|
||||
defer a.stateLock.Unlock()
|
||||
|
||||
idx := sort.Search(len(a.state.Sources), func(idx int) bool {
|
||||
return src.Compare(a.state.Sources[idx].Source) <= 0
|
||||
})
|
||||
if idx < 0 || idx >= len(a.state.Sources) || src.Compare(a.state.Sources[idx].Source) != 0 {
|
||||
a.logger.Warnf(
|
||||
"cannot found target file, real_path= %s found_index= %d tape_file_path= %v", src.RealPath(), idx,
|
||||
lo.Map(a.state.Sources, func(source *entity.SourceState, _ int) string { return source.Source.RealPath() }))
|
||||
return
|
||||
}
|
||||
|
||||
target := a.state.Sources[idx]
|
||||
if target == nil || !src.Equal(target.Source) {
|
||||
return
|
||||
}
|
||||
target.Status = targetStatus
|
||||
|
||||
if _, err := a.exe.SaveJob(ctx, a.job); err != nil {
|
||||
logrus.WithContext(ctx).Infof("save job for update file fail, name= %s", job.Base+path.Join(job.Path...))
|
||||
}
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
defer func() {
|
||||
ctx := tools.WithoutTimeout(ctx)
|
||||
|
||||
// if tape drop to readonly, ltfs cannot write index to partition a.
|
||||
// rollback sources for next try.
|
||||
if dropToReadonly {
|
||||
a.logger.WithContext(ctx).Errorf("tape filesystem had droped to readonly, rollback, barcode= '%s'", barcode)
|
||||
a.rollbackSources(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
report := reportGetter()
|
||||
sort.Slice(report.Jobs, func(i, j int) bool {
|
||||
return entity.NewSourceFromACPJob(report.Jobs[i]).Compare(entity.NewSourceFromACPJob(report.Jobs[j])) < 0
|
||||
})
|
||||
|
||||
reportFile, err := a.exe.newReportWriter(barcode)
|
||||
if err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Warnf("open report file fail, barcode= '%s'", barcode)
|
||||
} else {
|
||||
defer reportFile.Close()
|
||||
tools.WrapWithLogger(ctx, a.logger, func() {
|
||||
reportFile.Write([]byte(report.ToJSONString(false)))
|
||||
})
|
||||
}
|
||||
|
||||
filteredJobs := make([]*acp.Job, 0, len(report.Jobs))
|
||||
files := make([]*library.TapeFile, 0, len(report.Jobs))
|
||||
for _, job := range report.Jobs {
|
||||
if len(job.SuccessTargets) == 0 {
|
||||
continue
|
||||
}
|
||||
if !job.Mode.IsRegular() {
|
||||
continue
|
||||
}
|
||||
|
||||
hash, err := hex.DecodeString(job.SHA256)
|
||||
if err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Warnf("decode sha256 fail, path= '%s'", entity.NewSourceFromACPJob(job).RealPath())
|
||||
continue
|
||||
}
|
||||
|
||||
files = append(files, &library.TapeFile{
|
||||
Path: path.Join(job.Path...),
|
||||
Size: job.Size,
|
||||
Mode: job.Mode,
|
||||
ModTime: job.ModTime,
|
||||
WriteTime: job.WriteTime,
|
||||
Hash: hash,
|
||||
})
|
||||
filteredJobs = append(filteredJobs, job)
|
||||
}
|
||||
|
||||
tape, err := a.exe.lib.CreateTape(ctx, &library.Tape{
|
||||
Barcode: barcode,
|
||||
Name: name,
|
||||
Encryption: encryption,
|
||||
CreateTime: time.Now(),
|
||||
}, files)
|
||||
if err != nil {
|
||||
rerr = tools.AppendError(rerr, fmt.Errorf("create tape fail, barcode= '%s' name= '%s', %w", barcode, name, err))
|
||||
return
|
||||
}
|
||||
a.logger.Infof("create tape success, tape_id= %d", tape.ID)
|
||||
|
||||
if err := a.exe.lib.TrimFiles(ctx); err != nil {
|
||||
a.logger.WithError(err).Warnf("trim library files fail")
|
||||
}
|
||||
|
||||
if err := a.markSourcesAsSubmited(ctx, filteredJobs); err != nil {
|
||||
rerr = tools.AppendError(rerr, fmt.Errorf("mark source as submited fail, %w", err))
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
copyer, err := acp.New(ctx, opts...)
|
||||
if err != nil {
|
||||
rerr = fmt.Errorf("start copy fail, %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
copyer.Wait()
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) switchStep(ctx context.Context, target entity.JobArchiveStep, status entity.JobStatus, expect mapset.Set[entity.JobArchiveStep]) error {
|
||||
a.stateLock.Lock()
|
||||
defer a.stateLock.Unlock()
|
||||
|
||||
if !expect.Contains(a.state.Step) {
|
||||
return fmt.Errorf("unexpected current step, target= '%s' expect= '%s' has= '%s'", target, expect, a.state.Step)
|
||||
}
|
||||
|
||||
a.state.Step = target
|
||||
a.job.Status = status
|
||||
if _, err := a.exe.SaveJob(ctx, a.job); err != nil {
|
||||
return fmt.Errorf("switch to step copying, save job fail, %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) markSourcesAsSubmited(ctx context.Context, jobs []*acp.Job) error {
|
||||
a.stateLock.Lock()
|
||||
defer a.stateLock.Unlock()
|
||||
|
||||
searchableSource := a.state.Sources[:]
|
||||
for _, job := range jobs {
|
||||
src := entity.NewSourceFromACPJob(job)
|
||||
for idx, testSrc := range searchableSource {
|
||||
if src.Compare(testSrc.Source) <= 0 {
|
||||
searchableSource = searchableSource[idx:]
|
||||
break
|
||||
}
|
||||
return a.updateJob(ctx, func(job *Job, state *entity.JobArchiveState) error {
|
||||
if !expect.Contains(state.Step) {
|
||||
return fmt.Errorf("unexpected current step, target= '%s' expect= '%s' has= '%s'", target, expect, state.Step)
|
||||
}
|
||||
|
||||
target := searchableSource[0]
|
||||
if target == nil || !src.Equal(target.Source) {
|
||||
continue
|
||||
}
|
||||
|
||||
target.Status = entity.CopyStatus_SUBMITED
|
||||
}
|
||||
|
||||
if _, err := a.exe.SaveJob(ctx, a.job); err != nil {
|
||||
return fmt.Errorf("mark sources as submited, save job, %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) rollbackSources(ctx context.Context) error {
|
||||
a.stateLock.Lock()
|
||||
defer a.stateLock.Unlock()
|
||||
|
||||
for _, source := range a.state.Sources {
|
||||
if source.Status == entity.CopyStatus_SUBMITED {
|
||||
continue
|
||||
}
|
||||
source.Status = entity.CopyStatus_PENDING
|
||||
}
|
||||
|
||||
if _, err := a.exe.SaveJob(ctx, a.job); err != nil {
|
||||
return fmt.Errorf("mark sources as submited, save job, %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) getTodoSources() int {
|
||||
a.stateLock.Lock()
|
||||
defer a.stateLock.Unlock()
|
||||
|
||||
var todo int
|
||||
for _, s := range a.state.Sources {
|
||||
if s.Status == entity.CopyStatus_SUBMITED {
|
||||
continue
|
||||
}
|
||||
todo++
|
||||
}
|
||||
|
||||
return todo
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) makeTapeFinished(ctx context.Context) {
|
||||
if a.getTodoSources() > 0 {
|
||||
a.submit(ctx, &entity.JobArchiveNextParam{Param: &entity.JobArchiveNextParam_WaitForTape{WaitForTape: &entity.JobArchiveWaitForTapeParam{}}})
|
||||
} else {
|
||||
a.submit(ctx, &entity.JobArchiveNextParam{Param: &entity.JobArchiveNextParam_Finished{Finished: &entity.JobArchiveFinishedParam{}}})
|
||||
}
|
||||
state.Step = target
|
||||
job.Status = status
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/samuelncui/acp"
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
)
|
||||
|
||||
func (a *jobArchiveExecutor) Initialize(ctx context.Context, param *entity.JobParam) error {
|
||||
if err := a.applyParam(ctx, param.GetArchive()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.dispatch(ctx, &entity.JobArchiveDispatchParam{Param: &entity.JobArchiveDispatchParam_WaitForTape{
|
||||
WaitForTape: &entity.JobArchiveWaitForTapeParam{},
|
||||
}})
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) applyParam(ctx context.Context, param *entity.JobArchiveParam) error {
|
||||
if param == nil {
|
||||
return fmt.Errorf("archive param is nil")
|
||||
}
|
||||
|
||||
return a.updateJob(ctx, func(_ *Job, state *entity.JobArchiveState) error {
|
||||
var err error
|
||||
sources := make([]*entity.SourceState, 0, len(param.Sources)*8)
|
||||
for _, src := range param.Sources {
|
||||
src.Base = strings.TrimSpace(src.Base)
|
||||
if src.Base[0] != '/' {
|
||||
src.Base = path.Join(a.exe.paths.Source, src.Base) + "/"
|
||||
}
|
||||
|
||||
sources, err = a.walk(ctx, src, sources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
sort.Slice(sources, func(i, j int) bool {
|
||||
return sources[i].Source.Compare(sources[j].Source) < 0
|
||||
})
|
||||
|
||||
for idx, src := range sources {
|
||||
if idx > 0 && sources[idx-1].Source.Equal(src.Source) {
|
||||
return fmt.Errorf("have multi file with same path, path= %s", src.Source.RealPath())
|
||||
}
|
||||
}
|
||||
|
||||
state.Step = entity.JobArchiveStep_PENDING
|
||||
state.Sources = sources
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) walk(ctx context.Context, src *entity.Source, sources []*entity.SourceState) ([]*entity.SourceState, error) {
|
||||
path := src.RealPath()
|
||||
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("walk get stat, path= '%s', %w", path, err)
|
||||
}
|
||||
|
||||
mode := stat.Mode()
|
||||
if mode.IsRegular() {
|
||||
if stat.Name() == ".DS_Store" {
|
||||
return sources, nil
|
||||
}
|
||||
return append(sources, &entity.SourceState{
|
||||
Source: src,
|
||||
Size: stat.Size(),
|
||||
Status: entity.CopyStatus_PENDING,
|
||||
}), nil
|
||||
}
|
||||
if mode&acp.UnexpectFileMode != 0 {
|
||||
return sources, nil
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("walk read dir, path= '%s', %w", path, err)
|
||||
}
|
||||
for _, file := range files {
|
||||
sources, err = a.walk(ctx, src.Append(file.Name()), sources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return sources, nil
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/samuelncui/acp"
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
)
|
||||
|
||||
func (e *Executor) createArchive(ctx context.Context, job *Job, param *entity.JobArchiveParam) error {
|
||||
var err error
|
||||
sources := make([]*entity.SourceState, 0, len(param.Sources)*8)
|
||||
for _, src := range param.Sources {
|
||||
src.Base = strings.TrimSpace(src.Base)
|
||||
if src.Base[0] != '/' {
|
||||
src.Base = path.Join(e.paths.Source, src.Base) + "/"
|
||||
}
|
||||
|
||||
sources, err = walk(ctx, src, sources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
sort.Slice(sources, func(i, j int) bool {
|
||||
return sources[i].Source.Compare(sources[j].Source) < 0
|
||||
})
|
||||
|
||||
for idx, src := range sources {
|
||||
if idx > 0 && sources[idx-1].Source.Equal(src.Source) {
|
||||
return fmt.Errorf("have multi file with same path, path= %s", src.Source.RealPath())
|
||||
}
|
||||
}
|
||||
|
||||
job.State = &entity.JobState{State: &entity.JobState_Archive{Archive: &entity.JobArchiveState{
|
||||
Step: entity.JobArchiveStep_PENDING,
|
||||
Sources: sources,
|
||||
}}}
|
||||
return nil
|
||||
}
|
||||
|
||||
func walk(ctx context.Context, src *entity.Source, sources []*entity.SourceState) ([]*entity.SourceState, error) {
|
||||
path := src.RealPath()
|
||||
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("walk get stat, path= '%s', %w", path, err)
|
||||
}
|
||||
|
||||
mode := stat.Mode()
|
||||
if mode.IsRegular() {
|
||||
if stat.Name() == ".DS_Store" {
|
||||
return sources, nil
|
||||
}
|
||||
return append(sources, &entity.SourceState{
|
||||
Source: src,
|
||||
Size: stat.Size(),
|
||||
Status: entity.CopyStatus_PENDING,
|
||||
}), nil
|
||||
}
|
||||
if mode&acp.UnexpectFileMode != 0 {
|
||||
return sources, nil
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("walk read dir, path= '%s', %w", path, err)
|
||||
}
|
||||
for _, file := range files {
|
||||
sources, err = walk(ctx, src.Append(file.Name()), sources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return sources, nil
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
)
|
||||
|
||||
func (e *Executor) startArchive(ctx context.Context, job *Job) error {
|
||||
return e.Submit(ctx, job, &entity.JobNextParam{Param: &entity.JobNextParam_Archive{
|
||||
Archive: &entity.JobArchiveNextParam{Param: &entity.JobArchiveNextParam_WaitForTape{
|
||||
WaitForTape: &entity.JobArchiveWaitForTapeParam{},
|
||||
}},
|
||||
}})
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/samuelncui/acp"
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
"github.com/samuelncui/yatm/library"
|
||||
"github.com/samuelncui/yatm/tools"
|
||||
)
|
||||
|
||||
func (a *jobArchiveExecutor) makeTape(ctx context.Context, device, barcode, name string) (rerr error) {
|
||||
barcode = strings.ToUpper(barcode)
|
||||
|
||||
state := a.getState()
|
||||
if state == nil {
|
||||
return fmt.Errorf("cannot found archive state, abort")
|
||||
}
|
||||
|
||||
if !a.exe.OccupyDevice(device) {
|
||||
return fmt.Errorf("device is using, device= %s", device)
|
||||
}
|
||||
defer a.exe.ReleaseDevice(device)
|
||||
|
||||
defer a.makeTapeFinished(tools.WithoutTimeout(ctx))
|
||||
encryption, keyPath, keyRecycle, err := a.exe.newKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer keyRecycle()
|
||||
|
||||
if err := runCmd(a.logger, a.exe.makeEncryptCmd(ctx, device, keyPath, barcode, name)); err != nil {
|
||||
return fmt.Errorf("run encrypt script fail, %w", err)
|
||||
}
|
||||
|
||||
mkfsCmd := exec.CommandContext(ctx, a.exe.scripts.Mkfs)
|
||||
mkfsCmd.Env = append(mkfsCmd.Env, fmt.Sprintf("DEVICE=%s", device), fmt.Sprintf("TAPE_BARCODE=%s", barcode), fmt.Sprintf("TAPE_NAME=%s", name))
|
||||
if err := runCmd(a.logger, mkfsCmd); err != nil {
|
||||
return fmt.Errorf("run mkfs script fail, %w", err)
|
||||
}
|
||||
|
||||
mountPoint, err := os.MkdirTemp("", "*.ltfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp mountpoint, %w", err)
|
||||
}
|
||||
|
||||
mountCmd := exec.CommandContext(ctx, a.exe.scripts.Mount)
|
||||
mountCmd.Env = append(mountCmd.Env, fmt.Sprintf("DEVICE=%s", device), fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
if err := runCmd(a.logger, mountCmd); err != nil {
|
||||
return fmt.Errorf("run mount script fail, %w", err)
|
||||
}
|
||||
defer func() {
|
||||
umountCmd := exec.CommandContext(tools.WithoutTimeout(ctx), a.exe.scripts.Umount)
|
||||
umountCmd.Env = append(umountCmd.Env, fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
if err := runCmd(a.logger, umountCmd); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("run umount script fail, %s", mountPoint)
|
||||
return
|
||||
}
|
||||
if err := os.Remove(mountPoint); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("remove mount point fail, %s", mountPoint)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
wildcardJobOpts := make([]acp.WildcardJobOption, 0, 6)
|
||||
wildcardJobOpts = append(wildcardJobOpts, acp.Target(mountPoint))
|
||||
for _, source := range state.Sources {
|
||||
if source.Status == entity.CopyStatus_SUBMITED {
|
||||
continue
|
||||
}
|
||||
wildcardJobOpts = append(wildcardJobOpts, acp.AccurateSource(source.Source.Base, source.Source.Path))
|
||||
}
|
||||
|
||||
opts := make([]acp.Option, 0, 4)
|
||||
opts = append(opts, acp.WildcardJob(wildcardJobOpts...))
|
||||
opts = append(opts, acp.WithHash(true))
|
||||
opts = append(opts, acp.SetToDevice(acp.LinearDevice(true)))
|
||||
opts = append(opts, acp.WithLogger(a.logger))
|
||||
|
||||
reportHander, reportGetter := acp.NewReportGetter()
|
||||
opts = append(opts, acp.WithEventHandler(reportHander))
|
||||
|
||||
a.progress = newProgress()
|
||||
defer func() { a.progress = nil }()
|
||||
|
||||
var dropToReadonly bool
|
||||
opts = append(opts, acp.WithEventHandler(func(ev acp.Event) {
|
||||
switch e := ev.(type) {
|
||||
case *acp.EventUpdateCount:
|
||||
atomic.StoreInt64(&a.progress.totalBytes, e.Bytes)
|
||||
atomic.StoreInt64(&a.progress.totalFiles, e.Files)
|
||||
return
|
||||
case *acp.EventUpdateProgress:
|
||||
a.progress.setBytes(e.Bytes)
|
||||
atomic.StoreInt64(&a.progress.files, e.Files)
|
||||
return
|
||||
case *acp.EventReportError:
|
||||
a.logger.WithContext(ctx).Errorf("acp report error, src= '%s' dst= '%s' err= '%s'", e.Error.Src, e.Error.Dst, e.Error.Err)
|
||||
return
|
||||
case *acp.EventUpdateJob:
|
||||
job := e.Job
|
||||
src := entity.NewSourceFromACPJob(job)
|
||||
|
||||
var targetStatus entity.CopyStatus
|
||||
switch job.Status {
|
||||
case acp.JobStatusPending, acp.JobStatusPreparing:
|
||||
targetStatus = entity.CopyStatus_PENDING
|
||||
case acp.JobStatusCopying:
|
||||
targetStatus = entity.CopyStatus_RUNNING
|
||||
case acp.JobStatusFinished:
|
||||
targetStatus = entity.CopyStatus_FAILED
|
||||
if len(job.SuccessTargets) > 0 {
|
||||
a.logger.WithContext(ctx).Infof("file '%s' copy success, size= %d", src.RealPath(), job.Size)
|
||||
targetStatus = entity.CopyStatus_STAGED
|
||||
break // break from switch
|
||||
}
|
||||
|
||||
for dst, err := range job.FailTargets {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, acp.ErrTargetNoSpace) {
|
||||
continue
|
||||
}
|
||||
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("file '%s' copy fail, dst= '%s'", src.RealPath(), dst)
|
||||
if errors.Is(err, acp.ErrTargetDropToReadonly) {
|
||||
dropToReadonly = true
|
||||
}
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
a.updateJob(ctx, func(_ *Job, state *entity.JobArchiveState) error {
|
||||
idx := sort.Search(len(state.Sources), func(idx int) bool {
|
||||
return src.Compare(state.Sources[idx].Source) <= 0
|
||||
})
|
||||
if idx < 0 || idx >= len(state.Sources) || src.Compare(state.Sources[idx].Source) != 0 {
|
||||
return fmt.Errorf(
|
||||
"cannot found target file, real_path= %s found_index= %d tape_file_path= %v", src.RealPath(), idx,
|
||||
lo.Map(state.Sources, func(source *entity.SourceState, _ int) string { return source.Source.RealPath() }),
|
||||
)
|
||||
}
|
||||
|
||||
founded := state.Sources[idx]
|
||||
if founded == nil || !src.Equal(founded.Source) {
|
||||
return fmt.Errorf(
|
||||
"founded file not match, real_path= %s found_path= %s tape_file_path= %v", src.RealPath(), founded.Source.RealPath(),
|
||||
lo.Map(state.Sources, func(source *entity.SourceState, _ int) string { return source.Source.RealPath() }),
|
||||
)
|
||||
}
|
||||
|
||||
founded.Status = targetStatus
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
defer func() {
|
||||
ctx := tools.WithoutTimeout(ctx)
|
||||
|
||||
// if tape drop to readonly, ltfs cannot write index to partition a.
|
||||
// rollback sources for next try.
|
||||
if dropToReadonly {
|
||||
a.logger.WithContext(ctx).Errorf("tape filesystem had droped to readonly, rollback, barcode= '%s'", barcode)
|
||||
a.rollbackSources(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
report := reportGetter()
|
||||
sort.Slice(report.Jobs, func(i, j int) bool {
|
||||
return entity.NewSourceFromACPJob(report.Jobs[i]).Compare(entity.NewSourceFromACPJob(report.Jobs[j])) < 0
|
||||
})
|
||||
|
||||
reportFile, err := a.exe.newReportWriter(barcode)
|
||||
if err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Warnf("open report file fail, barcode= '%s'", barcode)
|
||||
} else {
|
||||
defer reportFile.Close()
|
||||
tools.WrapWithLogger(ctx, a.logger, func() {
|
||||
reportFile.Write([]byte(report.ToJSONString(false)))
|
||||
})
|
||||
}
|
||||
|
||||
filteredJobs := make([]*acp.Job, 0, len(report.Jobs))
|
||||
files := make([]*library.TapeFile, 0, len(report.Jobs))
|
||||
for _, job := range report.Jobs {
|
||||
if len(job.SuccessTargets) == 0 {
|
||||
continue
|
||||
}
|
||||
if !job.Mode.IsRegular() {
|
||||
continue
|
||||
}
|
||||
|
||||
hash, err := hex.DecodeString(job.SHA256)
|
||||
if err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Warnf("decode sha256 fail, path= '%s'", entity.NewSourceFromACPJob(job).RealPath())
|
||||
continue
|
||||
}
|
||||
|
||||
files = append(files, &library.TapeFile{
|
||||
Path: path.Join(job.Path...),
|
||||
Size: job.Size,
|
||||
Mode: job.Mode,
|
||||
ModTime: job.ModTime,
|
||||
WriteTime: job.WriteTime,
|
||||
Hash: hash,
|
||||
})
|
||||
filteredJobs = append(filteredJobs, job)
|
||||
}
|
||||
|
||||
tape, err := a.exe.lib.CreateTape(ctx, &library.Tape{
|
||||
Barcode: barcode,
|
||||
Name: name,
|
||||
Encryption: encryption,
|
||||
CreateTime: time.Now(),
|
||||
}, files)
|
||||
if err != nil {
|
||||
rerr = tools.AppendError(rerr, fmt.Errorf("create tape fail, barcode= '%s' name= '%s', %w", barcode, name, err))
|
||||
return
|
||||
}
|
||||
a.logger.Infof("create tape success, tape_id= %d", tape.ID)
|
||||
|
||||
if err := a.exe.lib.TrimFiles(ctx); err != nil {
|
||||
a.logger.WithError(err).Warnf("trim library files fail")
|
||||
}
|
||||
|
||||
if err := a.markSourcesAsSubmited(ctx, filteredJobs); err != nil {
|
||||
rerr = tools.AppendError(rerr, fmt.Errorf("mark source as submited fail, %w", err))
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
copyer, err := acp.New(ctx, opts...)
|
||||
if err != nil {
|
||||
rerr = fmt.Errorf("start copy fail, %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
copyer.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) markSourcesAsSubmited(ctx context.Context, jobs []*acp.Job) error {
|
||||
return a.updateJob(ctx, func(_ *Job, state *entity.JobArchiveState) error {
|
||||
searchableSource := state.Sources[:]
|
||||
for _, job := range jobs {
|
||||
src := entity.NewSourceFromACPJob(job)
|
||||
for idx, testSrc := range searchableSource {
|
||||
if src.Compare(testSrc.Source) <= 0 {
|
||||
searchableSource = searchableSource[idx:]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
target := searchableSource[0]
|
||||
if target == nil || !src.Equal(target.Source) {
|
||||
continue
|
||||
}
|
||||
|
||||
target.Status = entity.CopyStatus_SUBMITED
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) rollbackSources(ctx context.Context) error {
|
||||
return a.updateJob(ctx, func(_ *Job, state *entity.JobArchiveState) error {
|
||||
for _, source := range state.Sources {
|
||||
if source.Status == entity.CopyStatus_SUBMITED {
|
||||
continue
|
||||
}
|
||||
source.Status = entity.CopyStatus_PENDING
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) getTodoSources() int {
|
||||
state := a.getState()
|
||||
|
||||
var todo int
|
||||
for _, s := range state.Sources {
|
||||
if s.Status == entity.CopyStatus_SUBMITED {
|
||||
continue
|
||||
}
|
||||
todo++
|
||||
}
|
||||
|
||||
return todo
|
||||
}
|
||||
|
||||
func (a *jobArchiveExecutor) makeTapeFinished(ctx context.Context) {
|
||||
if a.getTodoSources() > 0 {
|
||||
a.dispatch(ctx, &entity.JobArchiveDispatchParam{Param: &entity.JobArchiveDispatchParam_WaitForTape{WaitForTape: &entity.JobArchiveWaitForTapeParam{}}})
|
||||
} else {
|
||||
a.dispatch(ctx, &entity.JobArchiveDispatchParam{Param: &entity.JobArchiveDispatchParam_Finished{Finished: &entity.JobArchiveFinishedParam{}}})
|
||||
}
|
||||
}
|
||||
+1
-57
@@ -1,59 +1,3 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/samuelncui/yatm/library"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (e *jobRestoreExecutor) loadTape(ctx context.Context, device string, tape *library.Tape) error {
|
||||
if !e.exe.occupyDevice(device) {
|
||||
return fmt.Errorf("device is using, device= %s", device)
|
||||
}
|
||||
defer e.exe.releaseDevice(device)
|
||||
|
||||
keyPath, keyRecycle, err := e.exe.restoreKey(tape.Encryption)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
time.Sleep(time.Second)
|
||||
keyRecycle()
|
||||
}()
|
||||
|
||||
logger := logrus.StandardLogger()
|
||||
|
||||
if err := runCmd(logger, e.exe.makeEncryptCmd(ctx, device, keyPath, tape.Barcode, tape.Name)); err != nil {
|
||||
return fmt.Errorf("run encrypt script fail, %w", err)
|
||||
}
|
||||
|
||||
mountPoint, err := os.MkdirTemp("", "*.ltfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp mountpoint, %w", err)
|
||||
}
|
||||
|
||||
mountCmd := exec.CommandContext(ctx, e.exe.scripts.Mount)
|
||||
mountCmd.Env = append(mountCmd.Env, fmt.Sprintf("DEVICE=%s", device), fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
if err := runCmd(logger, mountCmd); err != nil {
|
||||
return fmt.Errorf("run mount script fail, %w", err)
|
||||
}
|
||||
// defer func() {
|
||||
// umountCmd := exec.CommandContext(ctx, e.umountScript)
|
||||
// umountCmd.Env = append(umountCmd.Env, fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
// if err := runCmd(logger, umountCmd); err != nil {
|
||||
// logger.WithContext(ctx).WithError(err).Errorf("run umount script fail, %s", mountPoint)
|
||||
// return
|
||||
// }
|
||||
// if err := os.Remove(mountPoint); err != nil {
|
||||
// logger.WithContext(ctx).WithError(err).Errorf("remove mount point fail, %s", mountPoint)
|
||||
// return
|
||||
// }
|
||||
// }()
|
||||
|
||||
return nil
|
||||
}
|
||||
type jobTypeRestore struct{}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
)
|
||||
|
||||
func (e *Executor) getRestoreDisplay(ctx context.Context, job *Job) (*entity.JobRestoreDisplay, error) {
|
||||
display := new(entity.JobRestoreDisplay)
|
||||
|
||||
if exe := e.getRestoreExecutor(ctx, job); exe != nil && exe.progress != nil {
|
||||
display.CopiedBytes = atomic.LoadInt64(&exe.progress.bytes)
|
||||
display.CopiedFiles = atomic.LoadInt64(&exe.progress.files)
|
||||
display.TotalBytes = atomic.LoadInt64(&exe.progress.totalBytes)
|
||||
display.TotalFiles = atomic.LoadInt64(&exe.progress.totalFiles)
|
||||
display.StartTime = exe.progress.startTime.Unix()
|
||||
|
||||
speed := atomic.LoadInt64(&exe.progress.speed)
|
||||
display.Speed = &speed
|
||||
}
|
||||
|
||||
return display, nil
|
||||
}
|
||||
+79
-283
@@ -2,44 +2,30 @@ package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/samber/lo"
|
||||
"github.com/samuelncui/acp"
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
"github.com/samuelncui/yatm/tools"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
runningRestores sync.Map
|
||||
)
|
||||
type jobRestoreExecutor struct {
|
||||
lock sync.Mutex
|
||||
exe *Executor
|
||||
job *Job
|
||||
|
||||
func (e *Executor) getRestoreExecutor(ctx context.Context, job *Job) *jobRestoreExecutor {
|
||||
if running, has := runningRestores.Load(job.ID); has {
|
||||
return running.(*jobRestoreExecutor)
|
||||
}
|
||||
return nil
|
||||
progress *progress
|
||||
logFile *os.File
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
func (e *Executor) newRestoreExecutor(ctx context.Context, job *Job) (*jobRestoreExecutor, error) {
|
||||
if exe := e.getRestoreExecutor(ctx, job); exe != nil {
|
||||
return exe, nil
|
||||
}
|
||||
|
||||
logFile, err := e.newLogWriter(job.ID)
|
||||
func (*jobTypeRestore) GetExecutor(ctx context.Context, exe *Executor, job *Job) (JobExecutor, error) {
|
||||
logFile, err := exe.newLogWriter(job.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get log writer fail, %w", err)
|
||||
}
|
||||
@@ -47,39 +33,27 @@ func (e *Executor) newRestoreExecutor(ctx context.Context, job *Job) (*jobRestor
|
||||
logger := logrus.New()
|
||||
logger.SetOutput(io.MultiWriter(os.Stderr, logFile))
|
||||
|
||||
exe := &jobRestoreExecutor{
|
||||
exe: e,
|
||||
e := &jobRestoreExecutor{
|
||||
exe: exe,
|
||||
job: job,
|
||||
|
||||
state: job.State.GetRestore(),
|
||||
|
||||
logFile: logFile,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
runningRestores.Store(job.ID, exe)
|
||||
return exe, nil
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type jobRestoreExecutor struct {
|
||||
exe *Executor
|
||||
job *Job
|
||||
|
||||
stateLock sync.Mutex
|
||||
state *entity.JobRestoreState
|
||||
|
||||
progress *progress
|
||||
logFile *os.File
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
func (a *jobRestoreExecutor) submit(ctx context.Context, param *entity.JobRestoreNextParam) {
|
||||
if err := a.handle(ctx, param); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Infof("handler param fail, param= %s", param)
|
||||
func (a *jobRestoreExecutor) Dispatch(ctx context.Context, next *entity.JobDispatchParam) error {
|
||||
param := next.GetRestore()
|
||||
if param == nil {
|
||||
return fmt.Errorf("unexpected next param type, unexpected= JobRestoreDispatchParam, has= %s", next)
|
||||
}
|
||||
|
||||
return a.dispatch(ctx, param)
|
||||
}
|
||||
|
||||
func (a *jobRestoreExecutor) handle(ctx context.Context, param *entity.JobRestoreNextParam) error {
|
||||
func (a *jobRestoreExecutor) dispatch(ctx context.Context, param *entity.JobRestoreDispatchParam) error {
|
||||
if p := param.GetCopying(); p != nil {
|
||||
if err := a.switchStep(
|
||||
ctx, entity.JobRestoreStep_COPYING, entity.JobStatus_PROCESSING,
|
||||
@@ -102,7 +76,7 @@ func (a *jobRestoreExecutor) handle(ctx context.Context, param *entity.JobRestor
|
||||
|
||||
if p := param.GetWaitForTape(); p != nil {
|
||||
return a.switchStep(
|
||||
ctx, entity.JobRestoreStep_WAIT_FOR_TAPE, entity.JobStatus_PROCESSING,
|
||||
ctx, entity.JobRestoreStep_WAIT_FOR_TAPE, entity.JobStatus_PENDING,
|
||||
mapset.NewThreadUnsafeSet(entity.JobRestoreStep_PENDING, entity.JobRestoreStep_COPYING),
|
||||
)
|
||||
}
|
||||
@@ -115,258 +89,80 @@ func (a *jobRestoreExecutor) handle(ctx context.Context, param *entity.JobRestor
|
||||
return err
|
||||
}
|
||||
|
||||
a.logFile.Close()
|
||||
runningRestores.Delete(a.job.ID)
|
||||
return nil
|
||||
return a.Close(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *jobRestoreExecutor) restoreTape(ctx context.Context, device string) (rerr error) {
|
||||
if !a.exe.occupyDevice(device) {
|
||||
return fmt.Errorf("device is using, device= %s", device)
|
||||
}
|
||||
defer a.exe.releaseDevice(device)
|
||||
defer func() {
|
||||
if _, found := lo.Find(a.state.Tapes, func(item *entity.RestoreTape) bool {
|
||||
return item.Status != entity.CopyStatus_SUBMITED
|
||||
}); found {
|
||||
a.submit(tools.WithoutTimeout(ctx), &entity.JobRestoreNextParam{
|
||||
Param: &entity.JobRestoreNextParam_WaitForTape{WaitForTape: &entity.JobRestoreWaitForTapeParam{}},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
a.submit(tools.WithoutTimeout(ctx), &entity.JobRestoreNextParam{
|
||||
Param: &entity.JobRestoreNextParam_Finished{Finished: &entity.JobRestoreFinishedParam{}},
|
||||
})
|
||||
}()
|
||||
|
||||
readInfoCmd := exec.CommandContext(ctx, a.exe.scripts.ReadInfo)
|
||||
readInfoCmd.Env = append(readInfoCmd.Env, fmt.Sprintf("DEVICE=%s", device))
|
||||
infoBuf, err := runCmdWithReturn(a.logger, readInfoCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("run read info script fail, %w", err)
|
||||
func (a *jobRestoreExecutor) Display(ctx context.Context) (*entity.JobDisplay, error) {
|
||||
p := a.progress
|
||||
if p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
barcode := jsoniter.Get(infoBuf, "barcode").ToString()
|
||||
if len(barcode) > 6 {
|
||||
barcode = barcode[:6]
|
||||
display := new(entity.JobRestoreDisplay)
|
||||
display.CopiedBytes = atomic.LoadInt64(&p.bytes)
|
||||
display.CopiedFiles = atomic.LoadInt64(&p.files)
|
||||
display.TotalBytes = atomic.LoadInt64(&p.totalBytes)
|
||||
display.TotalFiles = atomic.LoadInt64(&p.totalFiles)
|
||||
display.StartTime = p.startTime.Unix()
|
||||
|
||||
speed := atomic.LoadInt64(&p.speed)
|
||||
display.Speed = &speed
|
||||
|
||||
return &entity.JobDisplay{Display: &entity.JobDisplay_Restore{Restore: display}}, nil
|
||||
}
|
||||
|
||||
func (a *jobRestoreExecutor) Close(ctx context.Context) error {
|
||||
a.logFile.Close()
|
||||
a.exe.RemoveJobExecutor(ctx, a.job.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *jobRestoreExecutor) Logger() *logrus.Logger {
|
||||
return a.logger
|
||||
}
|
||||
|
||||
func (a *jobRestoreExecutor) getState() *entity.JobRestoreState {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
if a.job.State == nil || a.job.State.GetRestore() == nil {
|
||||
a.job.State = &entity.JobState{State: &entity.JobState_Restore{Restore: &entity.JobRestoreState{}}}
|
||||
}
|
||||
|
||||
restoreTape, found := lo.Find(a.state.Tapes, func(t *entity.RestoreTape) bool {
|
||||
return t.Barcode == barcode
|
||||
})
|
||||
if !found || restoreTape == nil {
|
||||
expects := lo.Map(a.state.Tapes, func(t *entity.RestoreTape, _ int) string { return t.Barcode })
|
||||
return fmt.Errorf("unexpected tape barcode in library, has= '%s' expect= %v", barcode, expects)
|
||||
}
|
||||
if restoreTape.Status == entity.CopyStatus_SUBMITED {
|
||||
return fmt.Errorf("unexpected restore tape state status, tape is restored, status= '%s'", restoreTape.Status)
|
||||
return a.job.State.GetRestore()
|
||||
}
|
||||
|
||||
func (a *jobRestoreExecutor) updateJob(ctx context.Context, change func(*Job, *entity.JobRestoreState) error) error {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
if a.job.State == nil || a.job.State.GetRestore() == nil {
|
||||
a.job.State = &entity.JobState{State: &entity.JobState_Restore{Restore: &entity.JobRestoreState{}}}
|
||||
}
|
||||
|
||||
tape, err := a.exe.lib.GetTape(ctx, restoreTape.TapeId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get tape info fail, barcode= '%s' id= %d, %w", restoreTape.Barcode, restoreTape.TapeId, err)
|
||||
if err := change(a.job, a.job.State.GetRestore()); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Warnf("update state failed while exec change callback")
|
||||
return fmt.Errorf("update state failed while exec change callback, %w", err)
|
||||
}
|
||||
if _, err := a.exe.SaveJob(ctx, a.job); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Warnf("update state failed while save job")
|
||||
return fmt.Errorf("update state failed while save job, %w", err)
|
||||
}
|
||||
|
||||
keyPath, keyRecycle, err := a.exe.restoreKey(tape.Encryption)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
time.Sleep(time.Second)
|
||||
keyRecycle()
|
||||
}()
|
||||
|
||||
if err := runCmd(a.logger, a.exe.makeEncryptCmd(ctx, device, keyPath, barcode, tape.Name)); err != nil {
|
||||
return fmt.Errorf("run encrypt script fail, %w", err)
|
||||
}
|
||||
|
||||
mountPoint, err := os.MkdirTemp("", "*.ltfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp mountpoint, %w", err)
|
||||
}
|
||||
sourcePath := tools.Cache(func(p string) string { return path.Join(mountPoint, p) })
|
||||
|
||||
mountCmd := exec.CommandContext(ctx, a.exe.scripts.Mount)
|
||||
mountCmd.Env = append(mountCmd.Env, fmt.Sprintf("DEVICE=%s", device), fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
if err := runCmd(a.logger, mountCmd); err != nil {
|
||||
return fmt.Errorf("run mount script fail, %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
umountCmd := exec.CommandContext(tools.WithoutTimeout(ctx), a.exe.scripts.Umount)
|
||||
umountCmd.Env = append(umountCmd.Env, fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
if err := runCmd(a.logger, umountCmd); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("run umount script fail, %s", mountPoint)
|
||||
return
|
||||
}
|
||||
if err := os.Remove(mountPoint); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("remove mount point fail, %s", mountPoint)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
opts := make([]acp.Option, 0, 16)
|
||||
for _, f := range restoreTape.Files {
|
||||
if f.Status == entity.CopyStatus_SUBMITED {
|
||||
continue
|
||||
}
|
||||
|
||||
opts = append(opts, acp.AccurateJob(sourcePath(f.TapePath), []string{path.Join(a.exe.paths.Target, f.TargetPath)}))
|
||||
}
|
||||
|
||||
opts = append(opts, acp.WithHash(true))
|
||||
opts = append(opts, acp.SetFromDevice(acp.LinearDevice(true)))
|
||||
opts = append(opts, acp.WithLogger(a.logger))
|
||||
|
||||
a.progress = newProgress()
|
||||
defer func() { a.progress = nil }()
|
||||
|
||||
convertPath := tools.Cache(func(p string) string { return strings.ReplaceAll(p, "/", "\x00") })
|
||||
opts = append(opts, acp.WithEventHandler(func(ev acp.Event) {
|
||||
switch e := ev.(type) {
|
||||
case *acp.EventUpdateCount:
|
||||
atomic.StoreInt64(&a.progress.totalBytes, e.Bytes)
|
||||
atomic.StoreInt64(&a.progress.totalFiles, e.Files)
|
||||
return
|
||||
case *acp.EventUpdateProgress:
|
||||
a.progress.setBytes(e.Bytes)
|
||||
atomic.StoreInt64(&a.progress.files, e.Files)
|
||||
return
|
||||
case *acp.EventReportError:
|
||||
a.logger.WithContext(ctx).Errorf("acp report error, src= '%s' dst= '%s' err= '%s'", e.Error.Src, e.Error.Dst, e.Error.Err)
|
||||
return
|
||||
case *acp.EventUpdateJob:
|
||||
job := e.Job
|
||||
src := entity.NewSourceFromACPJob(job)
|
||||
|
||||
var targetStatus entity.CopyStatus
|
||||
switch job.Status {
|
||||
case "pending":
|
||||
targetStatus = entity.CopyStatus_PENDING
|
||||
case "preparing":
|
||||
targetStatus = entity.CopyStatus_RUNNING
|
||||
case "finished":
|
||||
a.logger.WithContext(ctx).Infof("file '%s' copy finished, size= %d", src.RealPath(), job.Size)
|
||||
|
||||
targetStatus = entity.CopyStatus_STAGED
|
||||
if len(job.FailTargets) > 0 {
|
||||
targetStatus = entity.CopyStatus_FAILED
|
||||
}
|
||||
|
||||
for dst, err := range job.FailTargets {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("file '%s' copy fail, dst= '%s'", src.RealPath(), dst)
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
a.stateLock.Lock()
|
||||
defer a.stateLock.Unlock()
|
||||
|
||||
realPath := src.RealPath()
|
||||
idx := sort.Search(len(restoreTape.Files), func(idx int) bool {
|
||||
return convertPath(realPath) <= convertPath(sourcePath(restoreTape.Files[idx].TapePath))
|
||||
})
|
||||
if idx < 0 || idx >= len(restoreTape.Files) {
|
||||
a.logger.Warnf(
|
||||
"cannot found target file, real_path= %s found_index= %d tape_file_path= %v", realPath, idx,
|
||||
lo.Map(restoreTape.Files, func(file *entity.RestoreFile, _ int) string { return sourcePath(file.TapePath) }),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
targetFile := restoreTape.Files[idx]
|
||||
if targetFile == nil || realPath != sourcePath(targetFile.TapePath) {
|
||||
a.logger.Warnf(
|
||||
"cannot match target file, real_path= %s found_index= %d found_file_path= %s",
|
||||
realPath, idx, sourcePath(targetFile.TapePath),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if targetStatus == entity.CopyStatus_STAGED {
|
||||
if targetHash := hex.EncodeToString(targetFile.Hash); targetHash != job.SHA256 {
|
||||
targetStatus = entity.CopyStatus_FAILED
|
||||
|
||||
a.logger.Warnf(
|
||||
"copy checksum do not match target file hash, real_path= %s target_hash= %s copy_hash= %s",
|
||||
realPath, targetHash, job.SHA256,
|
||||
)
|
||||
}
|
||||
if targetSize := targetFile.Size; targetSize != job.Size {
|
||||
targetStatus = entity.CopyStatus_FAILED
|
||||
|
||||
a.logger.Warnf(
|
||||
"copy size do not match target file hash, real_path= %s target_size= %d copy_size= %d",
|
||||
realPath, targetSize, job.Size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
targetFile.Status = targetStatus
|
||||
if _, err := a.exe.SaveJob(ctx, a.job); err != nil {
|
||||
a.logger.WithContext(ctx).Infof("save job for update file fail, name= %s", job.Base+path.Join(job.Path...))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
restoreTape.Status = entity.CopyStatus_RUNNING
|
||||
if _, err := a.exe.SaveJob(tools.WithoutTimeout(ctx), a.job); err != nil {
|
||||
a.logger.WithContext(ctx).Infof("save job for submit tape fail, barcode= %s", restoreTape.Barcode)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
a.stateLock.Lock()
|
||||
defer a.stateLock.Unlock()
|
||||
|
||||
restoreTape.Status = entity.CopyStatus_SUBMITED
|
||||
for _, file := range restoreTape.Files {
|
||||
if file.Status == entity.CopyStatus_STAGED {
|
||||
file.Status = entity.CopyStatus_SUBMITED
|
||||
}
|
||||
|
||||
if file.Status != entity.CopyStatus_SUBMITED {
|
||||
restoreTape.Status = entity.CopyStatus_FAILED
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := a.exe.SaveJob(tools.WithoutTimeout(ctx), a.job); err != nil {
|
||||
a.logger.WithContext(ctx).Infof("save job for submit tape fail, barcode= %s", restoreTape.Barcode)
|
||||
}
|
||||
}()
|
||||
|
||||
copyer, err := acp.New(ctx, opts...)
|
||||
if err != nil {
|
||||
rerr = fmt.Errorf("start copy fail, %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
copyer.Wait()
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *jobRestoreExecutor) switchStep(ctx context.Context, target entity.JobRestoreStep, status entity.JobStatus, expect mapset.Set[entity.JobRestoreStep]) error {
|
||||
a.stateLock.Lock()
|
||||
defer a.stateLock.Unlock()
|
||||
return a.updateJob(ctx, func(job *Job, state *entity.JobRestoreState) error {
|
||||
if !expect.Contains(state.Step) {
|
||||
return fmt.Errorf("unexpected current step, target= '%s' expect= '%s' has= '%s'", target, expect, state.Step)
|
||||
}
|
||||
|
||||
if !expect.Contains(a.state.Step) {
|
||||
return fmt.Errorf("unexpected current step, target= '%s' expect= '%s' has= '%s'", target, expect, a.state.Step)
|
||||
}
|
||||
|
||||
a.state.Step = target
|
||||
a.job.Status = status
|
||||
if _, err := a.exe.SaveJob(ctx, a.job); err != nil {
|
||||
return fmt.Errorf("switch to step copying, save job fail, %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
state.Step = target
|
||||
job.Status = status
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
"github.com/samber/lo"
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
"github.com/samuelncui/yatm/library"
|
||||
"github.com/samuelncui/yatm/tools"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (a *jobRestoreExecutor) Initialize(ctx context.Context, param *entity.JobParam) error {
|
||||
if err := a.applyParam(ctx, param.GetRestore()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.dispatch(ctx, &entity.JobRestoreDispatchParam{Param: &entity.JobRestoreDispatchParam_WaitForTape{
|
||||
WaitForTape: &entity.JobRestoreWaitForTapeParam{},
|
||||
}})
|
||||
}
|
||||
|
||||
type restoreFile struct {
|
||||
*library.File
|
||||
target string
|
||||
}
|
||||
|
||||
func (a *jobRestoreExecutor) applyParam(ctx context.Context, param *entity.JobRestoreParam) error {
|
||||
if param == nil {
|
||||
return fmt.Errorf("restore param is nil")
|
||||
}
|
||||
|
||||
return a.updateJob(ctx, func(_ *Job, state *entity.JobRestoreState) error {
|
||||
exe := a.exe
|
||||
|
||||
files, err := exe.getRestoreFiles(ctx, param.FileIds...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get restore files fail, ids= %v, %w", param.FileIds, err)
|
||||
}
|
||||
|
||||
fileIDs := make([]int64, 0, len(files))
|
||||
for _, file := range files {
|
||||
fileIDs = append(fileIDs, file.ID)
|
||||
}
|
||||
|
||||
positions, err := exe.lib.MGetPositionByFileID(ctx, fileIDs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tapeMapping := make(map[int64]mapset.Set[int64], 4)
|
||||
for _, file := range files {
|
||||
for _, posi := range positions[file.ID] {
|
||||
set := tapeMapping[posi.TapeID]
|
||||
if set == nil {
|
||||
tapeMapping[posi.TapeID] = mapset.NewThreadUnsafeSet(file.ID)
|
||||
continue
|
||||
}
|
||||
set.Add(file.ID)
|
||||
}
|
||||
}
|
||||
|
||||
tapes, err := exe.lib.MGetTape(ctx, lo.Keys(tapeMapping)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for tapeID := range tapeMapping {
|
||||
if tape, has := tapes[tapeID]; has && tape != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.WithContext(ctx).Infof("tape not found, tape_id= %d", tapeID)
|
||||
delete(tapeMapping, tapeID)
|
||||
}
|
||||
|
||||
restoreTapes := make([]*entity.RestoreTape, 0, len(tapeMapping))
|
||||
for len(tapeMapping) > 0 {
|
||||
var maxTapeID int64
|
||||
for tapeID, files := range tapeMapping {
|
||||
if maxTapeID == 0 {
|
||||
maxTapeID = tapeID
|
||||
continue
|
||||
}
|
||||
|
||||
diff := files.Cardinality() - tapeMapping[maxTapeID].Cardinality()
|
||||
if diff > 0 {
|
||||
maxTapeID = tapeID
|
||||
continue
|
||||
}
|
||||
if diff < 0 {
|
||||
continue
|
||||
}
|
||||
if tapeID < maxTapeID {
|
||||
maxTapeID = tapeID
|
||||
continue
|
||||
}
|
||||
}
|
||||
if maxTapeID == 0 {
|
||||
return fmt.Errorf("max tape not found, tape_ids= %v", lo.Keys(tapeMapping))
|
||||
}
|
||||
|
||||
fileIDs := tapeMapping[maxTapeID]
|
||||
delete(tapeMapping, maxTapeID)
|
||||
if fileIDs.Cardinality() == 0 {
|
||||
continue
|
||||
}
|
||||
for i, f := range tapeMapping {
|
||||
tapeMapping[i] = f.Difference(fileIDs)
|
||||
}
|
||||
|
||||
targets := make([]*entity.RestoreFile, 0, fileIDs.Cardinality())
|
||||
for _, fileID := range fileIDs.ToSlice() {
|
||||
file := files[fileID]
|
||||
if file == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
posi := positions[fileID]
|
||||
if len(posi) == 0 {
|
||||
logrus.WithContext(ctx).Infof("file position not found, file_id= %d", fileID)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, p := range posi {
|
||||
if p.TapeID != maxTapeID {
|
||||
continue
|
||||
}
|
||||
|
||||
targets = append(targets, &entity.RestoreFile{
|
||||
FileId: file.ID,
|
||||
TapeId: p.TapeID,
|
||||
PositionId: p.ID,
|
||||
Status: entity.CopyStatus_PENDING,
|
||||
Size: file.Size,
|
||||
Hash: file.Hash,
|
||||
TapePath: p.Path,
|
||||
TargetPath: file.target,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
convertPath := tools.ThreadUnsafeCache(func(p string) string { return strings.ReplaceAll(p, "/", "\x00") })
|
||||
sort.Slice(targets, func(i, j int) bool {
|
||||
return convertPath(targets[i].TapePath) < convertPath(targets[j].TapePath)
|
||||
})
|
||||
|
||||
restoreTapes = append(restoreTapes, &entity.RestoreTape{
|
||||
TapeId: maxTapeID,
|
||||
Barcode: tapes[maxTapeID].Barcode,
|
||||
Status: entity.CopyStatus_PENDING,
|
||||
Files: targets,
|
||||
})
|
||||
}
|
||||
|
||||
state.Step = entity.JobRestoreStep_PENDING
|
||||
state.Tapes = restoreTapes
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Executor) getRestoreFiles(ctx context.Context, rootIDs ...int64) (map[int64]*restoreFile, error) {
|
||||
rootIDSet := mapset.NewThreadUnsafeSet(rootIDs...)
|
||||
for _, id := range rootIDs {
|
||||
parents, err := e.lib.ListParents(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(parents) <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, parent := range parents[:len(parents)-1] {
|
||||
if !rootIDSet.Contains(parent.ID) {
|
||||
continue
|
||||
}
|
||||
|
||||
rootIDSet.Remove(id)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rootIDs = rootIDSet.ToSlice()
|
||||
mapping, err := e.lib.MGetFile(ctx, rootIDs...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mget file fail, ids= %v, %w", rootIDs, err)
|
||||
}
|
||||
|
||||
files := make([]*restoreFile, 0, len(rootIDs)*8)
|
||||
visited := mapset.NewThreadUnsafeSet[int64]()
|
||||
for _, root := range mapping {
|
||||
if visited.Contains(root.ID) {
|
||||
continue
|
||||
}
|
||||
|
||||
visited.Add(root.ID)
|
||||
if !fs.FileMode(root.Mode).IsDir() {
|
||||
files = append(files, &restoreFile{File: root, target: root.Name})
|
||||
continue
|
||||
}
|
||||
|
||||
found, err := e.visitFiles(ctx, root.Name, nil, visited, root.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files = append(files, found...)
|
||||
}
|
||||
|
||||
results := make(map[int64]*restoreFile, len(files))
|
||||
for _, f := range files {
|
||||
results[f.ID] = f
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (e *Executor) visitFiles(ctx context.Context, path string, files []*restoreFile, visited mapset.Set[int64], parentID int64) ([]*restoreFile, error) {
|
||||
children, err := e.lib.List(ctx, parentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, child := range children {
|
||||
if visited.Contains(child.ID) {
|
||||
continue
|
||||
}
|
||||
|
||||
visited.Add(child.ID)
|
||||
|
||||
target := path + "/" + child.Name
|
||||
if !fs.FileMode(child.Mode).IsDir() {
|
||||
files = append(files, &restoreFile{File: child, target: target})
|
||||
continue
|
||||
}
|
||||
|
||||
files, err = e.visitFiles(ctx, target, files, visited, child.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/samuelncui/yatm/library"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (e *jobRestoreExecutor) loadTape(ctx context.Context, device string, tape *library.Tape) error {
|
||||
if !e.exe.OccupyDevice(device) {
|
||||
return fmt.Errorf("device is using, device= %s", device)
|
||||
}
|
||||
defer e.exe.ReleaseDevice(device)
|
||||
|
||||
keyPath, keyRecycle, err := e.exe.restoreKey(tape.Encryption)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
time.Sleep(time.Second)
|
||||
keyRecycle()
|
||||
}()
|
||||
|
||||
logger := logrus.StandardLogger()
|
||||
|
||||
if err := runCmd(logger, e.exe.makeEncryptCmd(ctx, device, keyPath, tape.Barcode, tape.Name)); err != nil {
|
||||
return fmt.Errorf("run encrypt script fail, %w", err)
|
||||
}
|
||||
|
||||
mountPoint, err := os.MkdirTemp("", "*.ltfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp mountpoint, %w", err)
|
||||
}
|
||||
|
||||
mountCmd := exec.CommandContext(ctx, e.exe.scripts.Mount)
|
||||
mountCmd.Env = append(mountCmd.Env, fmt.Sprintf("DEVICE=%s", device), fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
if err := runCmd(logger, mountCmd); err != nil {
|
||||
return fmt.Errorf("run mount script fail, %w", err)
|
||||
}
|
||||
// defer func() {
|
||||
// umountCmd := exec.CommandContext(ctx, e.umountScript)
|
||||
// umountCmd.Env = append(umountCmd.Env, fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
// if err := runCmd(logger, umountCmd); err != nil {
|
||||
// logger.WithContext(ctx).WithError(err).Errorf("run umount script fail, %s", mountPoint)
|
||||
// return
|
||||
// }
|
||||
// if err := os.Remove(mountPoint); err != nil {
|
||||
// logger.WithContext(ctx).WithError(err).Errorf("remove mount point fail, %s", mountPoint)
|
||||
// return
|
||||
// }
|
||||
// }()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
"github.com/samber/lo"
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
"github.com/samuelncui/yatm/library"
|
||||
"github.com/samuelncui/yatm/tools"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type restoreFile struct {
|
||||
*library.File
|
||||
target string
|
||||
}
|
||||
|
||||
func (e *Executor) createRestore(ctx context.Context, job *Job, param *entity.JobRestoreParam) error {
|
||||
files, err := e.getRestoreFiles(ctx, param.FileIds...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get restore files fail, ids= %v, %w", param.FileIds, err)
|
||||
}
|
||||
|
||||
fileIDs := make([]int64, 0, len(files))
|
||||
for _, file := range files {
|
||||
fileIDs = append(fileIDs, file.ID)
|
||||
}
|
||||
|
||||
positions, err := e.lib.MGetPositionByFileID(ctx, fileIDs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tapeMapping := make(map[int64]mapset.Set[int64], 4)
|
||||
for _, file := range files {
|
||||
for _, posi := range positions[file.ID] {
|
||||
set := tapeMapping[posi.TapeID]
|
||||
if set == nil {
|
||||
tapeMapping[posi.TapeID] = mapset.NewThreadUnsafeSet(file.ID)
|
||||
continue
|
||||
}
|
||||
set.Add(file.ID)
|
||||
}
|
||||
}
|
||||
|
||||
tapes, err := e.lib.MGetTape(ctx, lo.Keys(tapeMapping)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for tapeID := range tapeMapping {
|
||||
if tape, has := tapes[tapeID]; has && tape != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.WithContext(ctx).Infof("tape not found, tape_id= %d", tapeID)
|
||||
delete(tapeMapping, tapeID)
|
||||
}
|
||||
|
||||
restoreTapes := make([]*entity.RestoreTape, 0, len(tapeMapping))
|
||||
for len(tapeMapping) > 0 {
|
||||
var maxTapeID int64
|
||||
for tapeID, files := range tapeMapping {
|
||||
if maxTapeID == 0 {
|
||||
maxTapeID = tapeID
|
||||
continue
|
||||
}
|
||||
|
||||
diff := files.Cardinality() - tapeMapping[maxTapeID].Cardinality()
|
||||
if diff > 0 {
|
||||
maxTapeID = tapeID
|
||||
continue
|
||||
}
|
||||
if diff < 0 {
|
||||
continue
|
||||
}
|
||||
if tapeID < maxTapeID {
|
||||
maxTapeID = tapeID
|
||||
continue
|
||||
}
|
||||
}
|
||||
if maxTapeID == 0 {
|
||||
return fmt.Errorf("max tape not found, tape_ids= %v", lo.Keys(tapeMapping))
|
||||
}
|
||||
|
||||
fileIDs := tapeMapping[maxTapeID]
|
||||
delete(tapeMapping, maxTapeID)
|
||||
if fileIDs.Cardinality() == 0 {
|
||||
continue
|
||||
}
|
||||
for i, f := range tapeMapping {
|
||||
tapeMapping[i] = f.Difference(fileIDs)
|
||||
}
|
||||
|
||||
targets := make([]*entity.RestoreFile, 0, fileIDs.Cardinality())
|
||||
for _, fileID := range fileIDs.ToSlice() {
|
||||
file := files[fileID]
|
||||
if file == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
posi := positions[fileID]
|
||||
if len(posi) == 0 {
|
||||
logrus.WithContext(ctx).Infof("file position not found, file_id= %d", fileID)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, p := range posi {
|
||||
if p.TapeID != maxTapeID {
|
||||
continue
|
||||
}
|
||||
|
||||
targets = append(targets, &entity.RestoreFile{
|
||||
FileId: file.ID,
|
||||
TapeId: p.TapeID,
|
||||
PositionId: p.ID,
|
||||
Status: entity.CopyStatus_PENDING,
|
||||
Size: file.Size,
|
||||
Hash: file.Hash,
|
||||
TapePath: p.Path,
|
||||
TargetPath: file.target,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
convertPath := tools.Cache(func(p string) string { return strings.ReplaceAll(p, "/", "\x00") })
|
||||
sort.Slice(targets, func(i, j int) bool {
|
||||
return convertPath(targets[i].TapePath) < convertPath(targets[j].TapePath)
|
||||
})
|
||||
|
||||
restoreTapes = append(restoreTapes, &entity.RestoreTape{
|
||||
TapeId: maxTapeID,
|
||||
Barcode: tapes[maxTapeID].Barcode,
|
||||
Status: entity.CopyStatus_PENDING,
|
||||
Files: targets,
|
||||
})
|
||||
}
|
||||
|
||||
job.State = &entity.JobState{State: &entity.JobState_Restore{Restore: &entity.JobRestoreState{
|
||||
Step: entity.JobRestoreStep_PENDING,
|
||||
Tapes: restoreTapes,
|
||||
}}}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) getRestoreFiles(ctx context.Context, rootIDs ...int64) (map[int64]*restoreFile, error) {
|
||||
rootIDSet := mapset.NewThreadUnsafeSet(rootIDs...)
|
||||
for _, id := range rootIDs {
|
||||
parents, err := e.lib.ListParents(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(parents) <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, parent := range parents[:len(parents)-1] {
|
||||
if !rootIDSet.Contains(parent.ID) {
|
||||
continue
|
||||
}
|
||||
|
||||
rootIDSet.Remove(id)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rootIDs = rootIDSet.ToSlice()
|
||||
mapping, err := e.lib.MGetFile(ctx, rootIDs...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mget file fail, ids= %v, %w", rootIDs, err)
|
||||
}
|
||||
|
||||
files := make([]*restoreFile, 0, len(rootIDs)*8)
|
||||
visited := mapset.NewThreadUnsafeSet[int64]()
|
||||
for _, root := range mapping {
|
||||
if visited.Contains(root.ID) {
|
||||
continue
|
||||
}
|
||||
|
||||
visited.Add(root.ID)
|
||||
if !fs.FileMode(root.Mode).IsDir() {
|
||||
files = append(files, &restoreFile{File: root, target: root.Name})
|
||||
continue
|
||||
}
|
||||
|
||||
found, err := e.visitFiles(ctx, root.Name, nil, visited, root.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files = append(files, found...)
|
||||
}
|
||||
|
||||
results := make(map[int64]*restoreFile, len(files))
|
||||
for _, f := range files {
|
||||
results[f.ID] = f
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (e *Executor) visitFiles(ctx context.Context, path string, files []*restoreFile, visited mapset.Set[int64], parentID int64) ([]*restoreFile, error) {
|
||||
children, err := e.lib.List(ctx, parentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, child := range children {
|
||||
if visited.Contains(child.ID) {
|
||||
continue
|
||||
}
|
||||
|
||||
visited.Add(child.ID)
|
||||
|
||||
target := path + "/" + child.Name
|
||||
if !fs.FileMode(child.Mode).IsDir() {
|
||||
files = append(files, &restoreFile{File: child, target: target})
|
||||
continue
|
||||
}
|
||||
|
||||
files, err = e.visitFiles(ctx, target, files, visited, child.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
)
|
||||
|
||||
func (e *Executor) startRestore(ctx context.Context, job *Job) error {
|
||||
return e.Submit(ctx, job, &entity.JobNextParam{Param: &entity.JobNextParam_Restore{
|
||||
Restore: &entity.JobRestoreNextParam{Param: &entity.JobRestoreNextParam_WaitForTape{
|
||||
WaitForTape: &entity.JobRestoreWaitForTapeParam{},
|
||||
}},
|
||||
}})
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/samber/lo"
|
||||
"github.com/samuelncui/acp"
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
"github.com/samuelncui/yatm/tools"
|
||||
)
|
||||
|
||||
func (a *jobRestoreExecutor) restoreTape(ctx context.Context, device string) (rerr error) {
|
||||
if !a.exe.OccupyDevice(device) {
|
||||
return fmt.Errorf("device is using, device= %s", device)
|
||||
}
|
||||
defer a.exe.ReleaseDevice(device)
|
||||
|
||||
defer func() {
|
||||
tapes := a.getState().Tapes
|
||||
if _, found := lo.Find(tapes, func(item *entity.RestoreTape) bool {
|
||||
return item.Status != entity.CopyStatus_SUBMITED
|
||||
}); found {
|
||||
a.dispatch(tools.WithoutTimeout(ctx), &entity.JobRestoreDispatchParam{
|
||||
Param: &entity.JobRestoreDispatchParam_WaitForTape{WaitForTape: &entity.JobRestoreWaitForTapeParam{}},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
a.dispatch(tools.WithoutTimeout(ctx), &entity.JobRestoreDispatchParam{
|
||||
Param: &entity.JobRestoreDispatchParam_Finished{Finished: &entity.JobRestoreFinishedParam{}},
|
||||
})
|
||||
}()
|
||||
|
||||
readInfoCmd := exec.CommandContext(ctx, a.exe.scripts.ReadInfo)
|
||||
readInfoCmd.Env = append(readInfoCmd.Env, fmt.Sprintf("DEVICE=%s", device))
|
||||
infoBuf, err := runCmdWithReturn(a.logger, readInfoCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("run read info script fail, %w", err)
|
||||
}
|
||||
|
||||
barcode := jsoniter.Get(infoBuf, "barcode").ToString()
|
||||
if len(barcode) > 6 {
|
||||
barcode = barcode[:6]
|
||||
}
|
||||
barcode = strings.ToUpper(barcode)
|
||||
|
||||
tapes := a.getState().Tapes
|
||||
tape, found := lo.Find(tapes, func(t *entity.RestoreTape) bool {
|
||||
return t.Barcode == barcode
|
||||
})
|
||||
if !found || tape == nil {
|
||||
expects := lo.Map(tapes, func(t *entity.RestoreTape, _ int) string { return t.Barcode })
|
||||
return fmt.Errorf("unexpected tape barcode in library, has= '%s' expect= %v", barcode, expects)
|
||||
}
|
||||
if tape.Status == entity.CopyStatus_SUBMITED {
|
||||
return fmt.Errorf("unexpected restore tape state status, tape is restored, status= '%s'", tape.Status)
|
||||
}
|
||||
|
||||
libTape, err := a.exe.lib.GetTape(ctx, tape.TapeId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get tape info fail, barcode= '%s' id= %d, %w", tape.Barcode, tape.TapeId, err)
|
||||
}
|
||||
|
||||
keyPath, keyRecycle, err := a.exe.restoreKey(libTape.Encryption)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
time.Sleep(time.Second)
|
||||
keyRecycle()
|
||||
}()
|
||||
|
||||
if err := runCmd(a.logger, a.exe.makeEncryptCmd(ctx, device, keyPath, barcode, libTape.Name)); err != nil {
|
||||
return fmt.Errorf("run encrypt script fail, %w", err)
|
||||
}
|
||||
|
||||
mountPoint, err := os.MkdirTemp("", "*.ltfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp mountpoint, %w", err)
|
||||
}
|
||||
sourcePath := tools.ThreadUnsafeCache(func(p string) string { return path.Join(mountPoint, p) })
|
||||
|
||||
mountCmd := exec.CommandContext(ctx, a.exe.scripts.Mount)
|
||||
mountCmd.Env = append(mountCmd.Env, fmt.Sprintf("DEVICE=%s", device), fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
if err := runCmd(a.logger, mountCmd); err != nil {
|
||||
return fmt.Errorf("run mount script fail, %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
umountCmd := exec.CommandContext(tools.WithoutTimeout(ctx), a.exe.scripts.Umount)
|
||||
umountCmd.Env = append(umountCmd.Env, fmt.Sprintf("MOUNT_POINT=%s", mountPoint))
|
||||
if err := runCmd(a.logger, umountCmd); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("run umount script fail, %s", mountPoint)
|
||||
return
|
||||
}
|
||||
if err := os.Remove(mountPoint); err != nil {
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("remove mount point fail, %s", mountPoint)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
opts := make([]acp.Option, 0, 16)
|
||||
for _, f := range tape.Files {
|
||||
if f.Status == entity.CopyStatus_SUBMITED {
|
||||
continue
|
||||
}
|
||||
|
||||
opts = append(opts, acp.AccurateJob(sourcePath(f.TapePath), []string{path.Join(a.exe.paths.Target, f.TargetPath)}))
|
||||
}
|
||||
|
||||
opts = append(opts, acp.WithHash(true))
|
||||
opts = append(opts, acp.SetFromDevice(acp.LinearDevice(true)))
|
||||
opts = append(opts, acp.WithLogger(a.logger))
|
||||
|
||||
a.progress = newProgress()
|
||||
defer func() { a.progress = nil }()
|
||||
|
||||
convertPath := tools.ThreadUnsafeCache(func(p string) string { return strings.ReplaceAll(p, "/", "\x00") })
|
||||
opts = append(opts, acp.WithEventHandler(func(ev acp.Event) {
|
||||
switch e := ev.(type) {
|
||||
case *acp.EventUpdateCount:
|
||||
atomic.StoreInt64(&a.progress.totalBytes, e.Bytes)
|
||||
atomic.StoreInt64(&a.progress.totalFiles, e.Files)
|
||||
return
|
||||
case *acp.EventUpdateProgress:
|
||||
a.progress.setBytes(e.Bytes)
|
||||
atomic.StoreInt64(&a.progress.files, e.Files)
|
||||
return
|
||||
case *acp.EventReportError:
|
||||
a.logger.WithContext(ctx).Errorf("acp report error, src= '%s' dst= '%s' err= '%s'", e.Error.Src, e.Error.Dst, e.Error.Err)
|
||||
return
|
||||
case *acp.EventUpdateJob:
|
||||
job := e.Job
|
||||
src := entity.NewSourceFromACPJob(job)
|
||||
|
||||
var targetStatus entity.CopyStatus
|
||||
switch job.Status {
|
||||
case "pending":
|
||||
targetStatus = entity.CopyStatus_PENDING
|
||||
case "preparing":
|
||||
targetStatus = entity.CopyStatus_RUNNING
|
||||
case "finished":
|
||||
a.logger.WithContext(ctx).Infof("file '%s' copy finished, size= %d", src.RealPath(), job.Size)
|
||||
|
||||
targetStatus = entity.CopyStatus_STAGED
|
||||
if len(job.FailTargets) > 0 {
|
||||
targetStatus = entity.CopyStatus_FAILED
|
||||
}
|
||||
|
||||
for dst, err := range job.FailTargets {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
a.logger.WithContext(ctx).WithError(err).Errorf("file '%s' copy fail, dst= '%s'", src.RealPath(), dst)
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
realPath := src.RealPath()
|
||||
a.updateJob(ctx, func(_ *Job, state *entity.JobRestoreState) error {
|
||||
tape, has := lo.Find(state.Tapes, func(tape *entity.RestoreTape) bool { return tape.Barcode == barcode })
|
||||
if !has || tape == nil {
|
||||
return fmt.Errorf("cannot found tape, barcode= %s", barcode)
|
||||
}
|
||||
|
||||
idx := sort.Search(len(tape.Files), func(idx int) bool {
|
||||
return convertPath(realPath) <= convertPath(sourcePath(tape.Files[idx].TapePath))
|
||||
})
|
||||
if idx < 0 || idx >= len(tape.Files) {
|
||||
return fmt.Errorf(
|
||||
"cannot found target file, real_path= %s found_index= %d tape_file_path= %v", realPath, idx,
|
||||
lo.Map(tape.Files, func(file *entity.RestoreFile, _ int) string { return sourcePath(file.TapePath) }),
|
||||
)
|
||||
}
|
||||
|
||||
found := tape.Files[idx]
|
||||
if found == nil || realPath != sourcePath(found.TapePath) {
|
||||
return fmt.Errorf(
|
||||
"cannot match found file, real_path= %s found_index= %d found_file_path= %s",
|
||||
realPath, idx, sourcePath(found.TapePath),
|
||||
)
|
||||
}
|
||||
|
||||
if targetStatus == entity.CopyStatus_STAGED {
|
||||
if targetHash := hex.EncodeToString(found.Hash); targetHash != job.SHA256 {
|
||||
targetStatus = entity.CopyStatus_FAILED
|
||||
|
||||
a.logger.Warnf(
|
||||
"copy checksum do not match target file hash, real_path= %s target_hash= %s copy_hash= %s",
|
||||
realPath, targetHash, job.SHA256,
|
||||
)
|
||||
}
|
||||
if targetSize := found.Size; targetSize != job.Size {
|
||||
targetStatus = entity.CopyStatus_FAILED
|
||||
|
||||
a.logger.Warnf(
|
||||
"copy size do not match target file hash, real_path= %s target_size= %d copy_size= %d",
|
||||
realPath, targetSize, job.Size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
found.Status = targetStatus
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
a.updateJob(ctx, func(_ *Job, state *entity.JobRestoreState) error {
|
||||
tape, has := lo.Find(state.Tapes, func(tape *entity.RestoreTape) bool { return tape.Barcode == barcode })
|
||||
if !has || tape == nil {
|
||||
return fmt.Errorf("cannot found tape, barcode= %s", barcode)
|
||||
}
|
||||
|
||||
tape.Status = entity.CopyStatus_RUNNING
|
||||
return nil
|
||||
})
|
||||
|
||||
defer func() {
|
||||
a.updateJob(ctx, func(job *Job, state *entity.JobRestoreState) error {
|
||||
tape, has := lo.Find(state.Tapes, func(tape *entity.RestoreTape) bool { return tape.Barcode == barcode })
|
||||
if !has || tape == nil {
|
||||
return fmt.Errorf("cannot found tape, barcode= %s", barcode)
|
||||
}
|
||||
|
||||
tape.Status = entity.CopyStatus_SUBMITED
|
||||
for _, file := range tape.Files {
|
||||
if file.Status == entity.CopyStatus_STAGED {
|
||||
file.Status = entity.CopyStatus_SUBMITED
|
||||
}
|
||||
|
||||
if file.Status != entity.CopyStatus_SUBMITED {
|
||||
tape.Status = entity.CopyStatus_FAILED
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
|
||||
copyer, err := acp.New(ctx, opts...)
|
||||
if err != nil {
|
||||
rerr = fmt.Errorf("start copy fail, %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
copyer.Wait()
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/modern-go/reflect2"
|
||||
"github.com/samuelncui/yatm/entity"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
jobParamToTypes = map[uintptr]uintptr{
|
||||
reflect2.RTypeOf(&entity.JobParam_Archive{}): reflect2.RTypeOf(&entity.JobState_Archive{}),
|
||||
reflect2.RTypeOf(&entity.JobParam_Restore{}): reflect2.RTypeOf(&entity.JobState_Restore{}),
|
||||
}
|
||||
jobTypes = map[uintptr]JobType{
|
||||
reflect2.RTypeOf(&entity.JobState_Archive{}): new(jobTypeArchive),
|
||||
reflect2.RTypeOf(&entity.JobState_Restore{}): new(jobTypeRestore),
|
||||
}
|
||||
)
|
||||
|
||||
type JobType interface {
|
||||
GetExecutor(ctx context.Context, exe *Executor, job *Job) (JobExecutor, error)
|
||||
}
|
||||
|
||||
type JobExecutor interface {
|
||||
Initialize(ctx context.Context, param *entity.JobParam) error
|
||||
Dispatch(ctx context.Context, param *entity.JobDispatchParam) error
|
||||
Display(ctx context.Context) (*entity.JobDisplay, error)
|
||||
Close(ctx context.Context) error
|
||||
|
||||
Logger() *logrus.Logger
|
||||
}
|
||||
@@ -10,8 +10,6 @@
|
||||
"gen-proto": "protoc --ts_out ./src/entity --proto_path ../entity/ `ls ../entity/*.proto` && ./src/entity/gen_index.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@samuelncui/chonky": "^0.2.7",
|
||||
"@samuelncui/chonky-icon-fontawesome": "^0.2.7",
|
||||
"@emotion/react": "^11.10.4",
|
||||
"@emotion/styled": "^11.10.4",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||
@@ -25,6 +23,8 @@
|
||||
"@protobuf-ts/grpcweb-transport": "^2.8.2",
|
||||
"@protobuf-ts/runtime": "^2.8.2",
|
||||
"@protobuf-ts/runtime-rpc": "^2.8.2",
|
||||
"@samuelncui/chonky": "^0.2.7",
|
||||
"@samuelncui/chonky-icon-fontawesome": "^0.2.7",
|
||||
"fast-text-encoding": "^1.0.6",
|
||||
"filesize": "^10.0.5",
|
||||
"format-duration": "^2.0.0",
|
||||
@@ -37,6 +37,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-is": "^18.2.0",
|
||||
"react-router-dom": "^6.4.5",
|
||||
"react-virtuoso": "^4.6.1",
|
||||
"sort-by": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
import { Fragment, ChangeEvent, memo, useState, useMemo, useCallback, useContext } from "react";
|
||||
import { assert } from "@protobuf-ts/runtime";
|
||||
import format from "format-duration";
|
||||
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Box from "@mui/material/Box";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
|
||||
import { cli } from "../api";
|
||||
import { Job, JobDispatchRequest, CopyStatus, SourceState, JobStatus } from "../entity";
|
||||
import { JobArchiveCopyingParam, JobArchiveStep, JobArchiveDisplay, JobArchiveState } from "../entity";
|
||||
|
||||
import { formatFilesize } from "../tools";
|
||||
|
||||
import { JobCard } from "./job-card";
|
||||
import { RefreshContext } from "../pages/jobs";
|
||||
|
||||
export const ArchiveCard = ({ job, state, display }: { job: Job; state: JobArchiveState; display: JobArchiveDisplay | null }): JSX.Element => {
|
||||
const [fields, progress] = useMemo(() => {
|
||||
const totalFiles = state.sources.length;
|
||||
let submitedFiles = 0,
|
||||
submitedBytes = 0,
|
||||
totalBytes = 0;
|
||||
for (const file of state.sources) {
|
||||
totalBytes += Number(file.size);
|
||||
if (file.status !== CopyStatus.SUBMITED) {
|
||||
continue;
|
||||
}
|
||||
submitedFiles++;
|
||||
submitedBytes += Number(file.size);
|
||||
}
|
||||
|
||||
const copiedFiles = submitedFiles + Number(display?.copiedFiles || 0n);
|
||||
const copiedBytes = submitedBytes + Number(display?.copiedBytes || 0n);
|
||||
const avgSpeed = (() => {
|
||||
if (!display || !display.copiedBytes || !display.startTime) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
const duration = Date.now() / 1000 - Number(display.startTime);
|
||||
if (duration <= 0) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
return Number(display.copiedBytes) / duration;
|
||||
})();
|
||||
|
||||
const progress = (totalBytes > 0 ? copiedBytes / totalBytes : 1) * 100;
|
||||
const fields = [
|
||||
{ name: "Current Step", value: JobArchiveStep[state.step] },
|
||||
{ name: "Current Speed", value: display?.speed ? `${formatFilesize(display?.speed)}/s` : "--" },
|
||||
{ name: "Average Speed", value: !isNaN(avgSpeed) ? `${formatFilesize(avgSpeed)}/s` : "--" },
|
||||
{ name: "Estimated Time", value: !isNaN(avgSpeed) ? format(((totalBytes - copiedBytes) * 1000) / avgSpeed) : "--" },
|
||||
{ name: "Copied Files", value: copiedFiles },
|
||||
{ name: "Copied Bytes", value: formatFilesize(copiedBytes) },
|
||||
{ name: "Submited Files", value: submitedFiles },
|
||||
{ name: "Submited Bytes", value: formatFilesize(submitedBytes) },
|
||||
{ name: "Total Files", value: totalFiles },
|
||||
{ name: "Total Bytes", value: formatFilesize(totalBytes) },
|
||||
];
|
||||
|
||||
return [fields, progress];
|
||||
}, [state, display]);
|
||||
|
||||
return (
|
||||
<JobCard
|
||||
job={job}
|
||||
detail={
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{ paddingTop: "1em" }}>
|
||||
<LinearProgress variant="determinate" value={progress} />
|
||||
</Box>
|
||||
</Grid>
|
||||
{fields.map((field, idx) => (
|
||||
<Grid item xs={12} md={3} key={idx}>
|
||||
<Typography variant="body1">
|
||||
<b>{field.name}</b>: {field.value}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
}
|
||||
buttons={
|
||||
<Fragment>
|
||||
{state.step === JobArchiveStep.WAIT_FOR_TAPE && <NewTapeDialog key="NEW_TAPE" job={job} />}
|
||||
{job.status !== JobStatus.PROCESSING && <RollbackDialog key="ROLLBACK" jobID={job.id} state={state} />}
|
||||
<ArchiveViewFilesDialog key="ARCHIVE_VIEW_FILES" sources={state.sources} />
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const NewTapeDialog = ({ job }: { job: Job }) => {
|
||||
const refresh = useContext(RefreshContext);
|
||||
|
||||
const [devices, setDevices] = useState<string[]>([]);
|
||||
const [param, setParam] = useState<JobArchiveCopyingParam | null>(null);
|
||||
const handleClickOpen = async () => {
|
||||
const reply = await cli.deviceList({}).response;
|
||||
setDevices(reply.devices);
|
||||
setParam(JobArchiveCopyingParam.create());
|
||||
};
|
||||
const handleClose = () => {
|
||||
setParam(null);
|
||||
setDevices([]);
|
||||
};
|
||||
const handleChange = (key: keyof JobArchiveCopyingParam) => (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (param === null) {
|
||||
return;
|
||||
}
|
||||
setParam({ ...param, [key]: event.target.value });
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
if (!param) {
|
||||
return;
|
||||
}
|
||||
|
||||
const trimedParam: JobArchiveCopyingParam = {
|
||||
device: param.device,
|
||||
barcode: param.barcode.toUpperCase(),
|
||||
name: param.name,
|
||||
};
|
||||
assert(trimedParam.barcode.length === 6);
|
||||
|
||||
const req = makeArchiveCopyingParam(job.id, trimedParam);
|
||||
console.log("job dispatch start, request= ", req);
|
||||
|
||||
const reply = await cli.jobDispatch(req).response;
|
||||
console.log("job dispatch success, reply= ", reply);
|
||||
await refresh();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button size="small" onClick={handleClickOpen}>
|
||||
Load Tape
|
||||
</Button>
|
||||
{param && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"sm"} fullWidth>
|
||||
<DialogTitle>Load Tape</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>After load tape into tape drive, click 'Submit'</DialogContentText>
|
||||
<TextField select required margin="dense" label="Drive Device" fullWidth variant="standard" value={param.device} onChange={handleChange("device")}>
|
||||
{devices.map((device) => (
|
||||
<MenuItem key={device} value={device}>
|
||||
{device}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField required margin="dense" label="Tape Barcode" fullWidth variant="standard" value={param.barcode} onChange={handleChange("barcode")} />
|
||||
<TextField required margin="dense" label="Tape Name" fullWidth variant="standard" value={param.name} onChange={handleChange("name")} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button onClick={handleSubmit}>Submit</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const ArchiveViewFilesDialog = ({ sources }: { sources: SourceState[] }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button size="small" onClick={handleClickOpen}>
|
||||
View Files
|
||||
</Button>
|
||||
{open && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"lg"} fullWidth scroll="paper" sx={{ height: "100%" }} className="view-log-dialog">
|
||||
<DialogTitle>View Files</DialogTitle>
|
||||
<DialogContent dividers style={{ padding: 0 }}>
|
||||
<Virtuoso
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
totalCount={sources.length}
|
||||
itemContent={(idx) => {
|
||||
const src = sources[idx];
|
||||
if (!src || !src.source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem key={idx} component="div" disablePadding>
|
||||
<ListItemText
|
||||
primary={src.source.base + src.source.path.join("/")}
|
||||
secondary={`Size: ${formatFilesize(src.size)} Status: ${CopyStatus[src.status]}`}
|
||||
style={{ padding: 0, margin: 5 }}
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const RollbackDialog = ({ jobID, state }: { jobID: bigint; state: JobArchiveState }) => {
|
||||
const refresh = useContext(RefreshContext);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleClickItem = useCallback(
|
||||
async (idx: number) => {
|
||||
const found = state.sources[idx];
|
||||
if (!found || !found.source) {
|
||||
return;
|
||||
}
|
||||
|
||||
const path = found.source.base + found.source.path.join("/");
|
||||
if (!confirm(`Rollback to file '${path}', all files after this file (included) will be set to 'PENDING'.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sources = Array.from(state.sources);
|
||||
for (let i = idx; i < sources.length; i++) {
|
||||
sources[i].status = CopyStatus.PENDING;
|
||||
}
|
||||
|
||||
await cli.jobEditState({ id: jobID, state: { state: { oneofKind: "archive", archive: { ...state, sources } } } });
|
||||
alert(`Rollback to file '${path}' success!`);
|
||||
await refresh();
|
||||
},
|
||||
[state, refresh],
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button size="small" onClick={handleClickOpen}>
|
||||
Rollback
|
||||
</Button>
|
||||
{open && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"lg"} fullWidth scroll="paper" sx={{ height: "100%" }} className="view-log-dialog">
|
||||
<DialogTitle>Click File to Rollback</DialogTitle>
|
||||
<DialogContent dividers style={{ padding: 0 }}>
|
||||
<Virtuoso
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
totalCount={state.sources.length}
|
||||
itemContent={(idx) => {
|
||||
const src = state.sources[idx];
|
||||
if (!src || !src.source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem key={idx} component="div" disablePadding>
|
||||
<ListItemButton style={{ padding: 0 }} onClick={() => handleClickItem(idx)}>
|
||||
<ListItemText
|
||||
primary={src.source.base + src.source.path.join("/")}
|
||||
secondary={`Size: ${formatFilesize(src.size)} Status: ${CopyStatus[src.status]}`}
|
||||
style={{ padding: 0, margin: 5 }}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
function makeArchiveCopyingParam(jobID: bigint, param: JobArchiveCopyingParam): JobDispatchRequest {
|
||||
return {
|
||||
id: jobID,
|
||||
param: {
|
||||
param: {
|
||||
oneofKind: "archive",
|
||||
archive: {
|
||||
param: {
|
||||
oneofKind: "copying",
|
||||
copying: param,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useCallback, useContext } from "react";
|
||||
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Card from "@mui/material/Card";
|
||||
import CardActions from "@mui/material/CardActions";
|
||||
import CardContent from "@mui/material/CardContent";
|
||||
import Button from "@mui/material/Button";
|
||||
import Divider from "@mui/material/Divider";
|
||||
|
||||
import { cli } from "../api";
|
||||
import { Job, JobStatus, JobDeleteRequest } from "../entity";
|
||||
|
||||
import { ViewLogDialog } from "./job-log";
|
||||
import { RefreshContext } from "../pages/jobs";
|
||||
|
||||
const DeleteJobButton = ({ jobID }: { jobID: bigint }) => {
|
||||
const refresh = useContext(RefreshContext);
|
||||
const deleteJob = useCallback(async () => {
|
||||
await cli.jobDelete(JobDeleteRequest.create({ ids: [jobID] }));
|
||||
await refresh();
|
||||
}, [jobID]);
|
||||
|
||||
return (
|
||||
<Button size="small" onClick={deleteJob}>
|
||||
Delete Job
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const JobCard = ({ job, detail, buttons }: { job: Job; detail?: JSX.Element; buttons?: JSX.Element }) => {
|
||||
return (
|
||||
<Card sx={{ textAlign: "left" }} className="job-detail">
|
||||
<CardContent>
|
||||
<Typography sx={{ fontSize: 14 }} color="text.secondary" gutterBottom>
|
||||
{`${JobStatus[job.status]}`}
|
||||
</Typography>
|
||||
<Typography variant="h5" component="div">{`Job ${job.id} - ${job.state?.state.oneofKind?.toUpperCase()}`}</Typography>
|
||||
{detail}
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardActions>
|
||||
<div>{buttons}</div>
|
||||
<div style={{ marginLeft: "auto", marginRight: 0 }}>
|
||||
<ViewLogDialog key="VIEW_LOG" jobID={job.id} />
|
||||
<DeleteJobButton key="DELETE_JOB" jobID={job.id} />
|
||||
</div>
|
||||
</CardActions>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Fragment, useState, useRef, useEffect, useCallback } from "react";
|
||||
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
|
||||
import { cli } from "../api";
|
||||
import { sleep } from "../tools";
|
||||
|
||||
export const ViewLogDialog = ({ jobID }: { jobID: bigint }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button size="small" onClick={handleClickOpen}>
|
||||
View Log
|
||||
</Button>
|
||||
{open && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"lg"} fullWidth scroll="paper" sx={{ height: "100%" }} className="view-log-dialog">
|
||||
<DialogTitle>View Log</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<LogConsole jobId={jobID} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const LogConsole = ({ jobId }: { jobId: bigint }) => {
|
||||
const [log, setLog] = useState<string>("");
|
||||
const [offset, setOffset] = useState(0n);
|
||||
const bottom = useRef(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
const reply = await cli.jobGetLog({ jobId, offset: offset }).response;
|
||||
setLog(log + new TextDecoder().decode(reply.logs));
|
||||
setOffset(reply.offset);
|
||||
}, [log, setLog, offset, setOffset, bottom]);
|
||||
const refreshRef = useRef(refresh);
|
||||
refreshRef.current = refresh;
|
||||
|
||||
useEffect(() => {
|
||||
var timer: NodeJS.Timeout;
|
||||
(async () => {
|
||||
await refreshRef.current();
|
||||
if (bottom.current) {
|
||||
const bottomElem = bottom.current as HTMLElement;
|
||||
await sleep(10);
|
||||
bottomElem.scrollIntoView(true);
|
||||
await sleep(10);
|
||||
bottomElem.parentElement?.scrollBy(0, 100);
|
||||
}
|
||||
|
||||
timer = setInterval(() => refreshRef.current(), 2000);
|
||||
})();
|
||||
|
||||
return () => {
|
||||
if (!timer) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, [refresh]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<pre>{log || "loading..."}</pre>
|
||||
<div ref={bottom} />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,271 @@
|
||||
import { Fragment, ChangeEvent, useState, useMemo, useContext } from "react";
|
||||
import format from "format-duration";
|
||||
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Chip, { ChipProps } from "@mui/material/Chip";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
|
||||
import { TreeView, TreeItem } from "@mui/x-tree-view";
|
||||
|
||||
import { cli } from "../api";
|
||||
import { Job, JobDispatchRequest, CopyStatus, JobArchiveStep } from "../entity";
|
||||
import { JobRestoreCopyingParam, JobRestoreStep, JobRestoreDisplay, JobRestoreState } from "../entity";
|
||||
import { RestoreTape } from "../entity";
|
||||
|
||||
import { formatFilesize } from "../tools";
|
||||
|
||||
import { JobCard } from "./job-card";
|
||||
import { RefreshContext } from "../pages/jobs";
|
||||
|
||||
const tapeStatusToColor = (status: CopyStatus): ChipProps["color"] => {
|
||||
switch (status) {
|
||||
case CopyStatus.DRAFT:
|
||||
return "primary";
|
||||
case CopyStatus.PENDING:
|
||||
return "primary";
|
||||
case CopyStatus.RUNNING:
|
||||
return "secondary";
|
||||
case CopyStatus.STAGED:
|
||||
return "warning";
|
||||
case CopyStatus.SUBMITED:
|
||||
return "success";
|
||||
case CopyStatus.FAILED:
|
||||
return "error";
|
||||
default:
|
||||
return "default";
|
||||
}
|
||||
};
|
||||
|
||||
export const RestoreCard = ({ job, state, display }: { job: Job; state: JobRestoreState; display: JobRestoreDisplay | null }): JSX.Element => {
|
||||
const [fields, progress] = useMemo(() => {
|
||||
const totalFiles = state.tapes.reduce((count, tape) => count + tape.files.length, 0);
|
||||
let successFiles = 0,
|
||||
successBytes = 0,
|
||||
copiedFiles = Number(display?.copiedFiles || 0n),
|
||||
copiedBytes = Number(display?.copiedBytes || 0n),
|
||||
totalBytes = 0;
|
||||
for (const tape of state.tapes) {
|
||||
for (const file of tape.files) {
|
||||
totalBytes += Number(file.size);
|
||||
|
||||
if (file.status === CopyStatus.SUBMITED || file.status === CopyStatus.STAGED) {
|
||||
successFiles++;
|
||||
successBytes += Number(file.size);
|
||||
}
|
||||
|
||||
if (file.status === CopyStatus.SUBMITED) {
|
||||
copiedFiles++;
|
||||
copiedBytes += Number(file.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const avgSpeed = (() => {
|
||||
if (!display || !display.copiedBytes || !display.startTime) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
const duration = Date.now() / 1000 - Number(display.startTime);
|
||||
if (duration <= 0) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
return Number(display.copiedBytes) / duration;
|
||||
})();
|
||||
|
||||
const progress = (totalBytes > 0 ? copiedBytes / totalBytes : 1) * 100;
|
||||
const fields = [
|
||||
{ name: "Current Step", value: JobArchiveStep[state.step] },
|
||||
{ name: "Current Speed", value: display?.speed ? `${formatFilesize(display?.speed)}/s` : "--" },
|
||||
{ name: "Average Speed", value: !isNaN(avgSpeed) ? `${formatFilesize(avgSpeed)}/s` : "--" },
|
||||
{ name: "Estimated Time", value: !isNaN(avgSpeed) ? format(((totalBytes - copiedBytes) * 1000) / avgSpeed) : "--" },
|
||||
{ name: "Copied Files", value: copiedFiles },
|
||||
{ name: "Copied Bytes", value: formatFilesize(copiedBytes) },
|
||||
{ name: "Success Files", value: successFiles },
|
||||
{ name: "Success Bytes", value: formatFilesize(successBytes) },
|
||||
{ name: "Total Files", value: totalFiles },
|
||||
{ name: "Total Bytes", value: formatFilesize(totalBytes) },
|
||||
];
|
||||
|
||||
return [fields, progress];
|
||||
}, [state, display]);
|
||||
|
||||
return (
|
||||
<JobCard
|
||||
job={job}
|
||||
detail={
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{ paddingTop: "1em" }}>
|
||||
<LinearProgress variant="determinate" value={progress} />
|
||||
</Box>
|
||||
</Grid>
|
||||
{fields.map((field, idx) => (
|
||||
<Grid item xs={12} md={3} key={idx}>
|
||||
<Typography variant="body1">
|
||||
<b>{field.name}</b>: {field.value}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
<Grid item xs={12} md={12}>
|
||||
<Stack direction="row" spacing={1} style={{ flexWrap: "wrap" }}>
|
||||
{state.tapes.map((tape) => (
|
||||
<Chip label={`${tape.barcode}: ${CopyStatus[tape.status]}`} color={tapeStatusToColor(tape.status)} variant="outlined" key={`${tape.tapeId}`} />
|
||||
))}
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
}
|
||||
buttons={
|
||||
<Fragment>
|
||||
{state.step === JobRestoreStep.WAIT_FOR_TAPE && <LoadTapeDialog key="LOAD_TAPE" job={job} />}
|
||||
<RestoreViewFilesDialog key="RESTORE_VIEW_FILES" tapes={state.tapes} />
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadTapeDialog = ({ job }: { job: Job }) => {
|
||||
const refresh = useContext(RefreshContext);
|
||||
|
||||
const [devices, setDevices] = useState<string[] | null>(null);
|
||||
const [device, setDevice] = useState<string | null>(null);
|
||||
const handleClickOpen = async () => {
|
||||
const reply = await cli.deviceList({}).response;
|
||||
setDevices(reply.devices);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setDevices(null);
|
||||
setDevice(null);
|
||||
};
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setDevice(event.target.value);
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const trimedParam: JobRestoreCopyingParam = {
|
||||
device: device,
|
||||
};
|
||||
|
||||
const req = makeRestoreCopyingParam(job.id, trimedParam);
|
||||
console.log("job dispatch start, request= ", req);
|
||||
|
||||
const reply = await cli.jobDispatch(req).response;
|
||||
console.log("job dispatch success, reply= ", reply);
|
||||
await refresh();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button size="small" onClick={handleClickOpen}>
|
||||
Load Tape
|
||||
</Button>
|
||||
{devices && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"sm"} fullWidth>
|
||||
<DialogTitle>Load Tape</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>After load tape into tape drive, click 'Submit'</DialogContentText>
|
||||
<TextField select required margin="dense" label="Drive Device" fullWidth variant="standard" value={device} onChange={handleChange}>
|
||||
{devices.map((device) => (
|
||||
<MenuItem key={device} value={device}>
|
||||
{device}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button onClick={handleSubmit}>Submit</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const RestoreViewFilesDialog = ({ tapes }: { tapes: RestoreTape[] }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
const counts = useMemo(() => tapes.map((tape) => tape.files.length), [tapes]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button size="small" onClick={handleClickOpen}>
|
||||
View Files
|
||||
</Button>
|
||||
{open && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"lg"} fullWidth scroll="paper" sx={{ height: "100%" }} className="view-log-dialog">
|
||||
<DialogTitle>View Files</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<TreeView defaultCollapseIcon={<ExpandMoreIcon />} defaultExpandIcon={<ChevronRightIcon />}>
|
||||
{tapes.map((tape) => {
|
||||
if (!tape.files) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TreeItem label={tape.barcode} nodeId={`tape-${tape.tapeId}`}>
|
||||
{tape.files.map((file) => (
|
||||
<TreeItem
|
||||
label={
|
||||
<pre style={{ margin: 0 }}>
|
||||
{file.tapePath} <b>{CopyStatus[file.status]}</b>
|
||||
</pre>
|
||||
}
|
||||
nodeId={`file-${file.positionId}`}
|
||||
/>
|
||||
))}
|
||||
</TreeItem>
|
||||
);
|
||||
})}
|
||||
</TreeView>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
function makeRestoreCopyingParam(jobID: bigint, param: JobRestoreCopyingParam): JobDispatchRequest {
|
||||
return {
|
||||
id: jobID,
|
||||
param: {
|
||||
param: {
|
||||
oneofKind: "restore",
|
||||
restore: {
|
||||
param: {
|
||||
oneofKind: "copying",
|
||||
copying: param,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
+26
-26
@@ -13,8 +13,8 @@ import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
|
||||
import { MessageType } from "@protobuf-ts/runtime";
|
||||
import { JobRestoreDisplay } from "./job_restore";
|
||||
import { JobArchiveDisplay } from "./job_archive";
|
||||
import { JobRestoreNextParam } from "./job_restore";
|
||||
import { JobArchiveNextParam } from "./job_archive";
|
||||
import { JobRestoreDispatchParam } from "./job_restore";
|
||||
import { JobArchiveDispatchParam } from "./job_archive";
|
||||
import { JobRestoreState } from "./job_restore";
|
||||
import { JobArchiveState } from "./job_archive";
|
||||
import { JobRestoreParam } from "./job_restore";
|
||||
@@ -95,24 +95,24 @@ export interface JobState {
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message job.JobNextParam
|
||||
* @generated from protobuf message job.JobDispatchParam
|
||||
*/
|
||||
export interface JobNextParam {
|
||||
export interface JobDispatchParam {
|
||||
/**
|
||||
* @generated from protobuf oneof: param
|
||||
*/
|
||||
param: {
|
||||
oneofKind: "archive";
|
||||
/**
|
||||
* @generated from protobuf field: job_archive.JobArchiveNextParam archive = 1;
|
||||
* @generated from protobuf field: job_archive.JobArchiveDispatchParam archive = 1;
|
||||
*/
|
||||
archive: JobArchiveNextParam;
|
||||
archive: JobArchiveDispatchParam;
|
||||
} | {
|
||||
oneofKind: "restore";
|
||||
/**
|
||||
* @generated from protobuf field: job_restore.JobRestoreNextParam restore = 2;
|
||||
* @generated from protobuf field: job_restore.JobRestoreDispatchParam restore = 2;
|
||||
*/
|
||||
restore: JobRestoreNextParam;
|
||||
restore: JobRestoreDispatchParam;
|
||||
} | {
|
||||
oneofKind: undefined;
|
||||
};
|
||||
@@ -423,35 +423,35 @@ class JobState$Type extends MessageType<JobState> {
|
||||
*/
|
||||
export const JobState = new JobState$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class JobNextParam$Type extends MessageType<JobNextParam> {
|
||||
class JobDispatchParam$Type extends MessageType<JobDispatchParam> {
|
||||
constructor() {
|
||||
super("job.JobNextParam", [
|
||||
{ no: 1, name: "archive", kind: "message", oneof: "param", T: () => JobArchiveNextParam },
|
||||
{ no: 2, name: "restore", kind: "message", oneof: "param", T: () => JobRestoreNextParam }
|
||||
super("job.JobDispatchParam", [
|
||||
{ no: 1, name: "archive", kind: "message", oneof: "param", T: () => JobArchiveDispatchParam },
|
||||
{ no: 2, name: "restore", kind: "message", oneof: "param", T: () => JobRestoreDispatchParam }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<JobNextParam>): JobNextParam {
|
||||
create(value?: PartialMessage<JobDispatchParam>): JobDispatchParam {
|
||||
const message = { param: { oneofKind: undefined } };
|
||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<JobNextParam>(this, message, value);
|
||||
reflectionMergePartial<JobDispatchParam>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobNextParam): JobNextParam {
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobDispatchParam): JobDispatchParam {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* job_archive.JobArchiveNextParam archive */ 1:
|
||||
case /* job_archive.JobArchiveDispatchParam archive */ 1:
|
||||
message.param = {
|
||||
oneofKind: "archive",
|
||||
archive: JobArchiveNextParam.internalBinaryRead(reader, reader.uint32(), options, (message.param as any).archive)
|
||||
archive: JobArchiveDispatchParam.internalBinaryRead(reader, reader.uint32(), options, (message.param as any).archive)
|
||||
};
|
||||
break;
|
||||
case /* job_restore.JobRestoreNextParam restore */ 2:
|
||||
case /* job_restore.JobRestoreDispatchParam restore */ 2:
|
||||
message.param = {
|
||||
oneofKind: "restore",
|
||||
restore: JobRestoreNextParam.internalBinaryRead(reader, reader.uint32(), options, (message.param as any).restore)
|
||||
restore: JobRestoreDispatchParam.internalBinaryRead(reader, reader.uint32(), options, (message.param as any).restore)
|
||||
};
|
||||
break;
|
||||
default:
|
||||
@@ -465,13 +465,13 @@ class JobNextParam$Type extends MessageType<JobNextParam> {
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: JobNextParam, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* job_archive.JobArchiveNextParam archive = 1; */
|
||||
internalBinaryWrite(message: JobDispatchParam, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* job_archive.JobArchiveDispatchParam archive = 1; */
|
||||
if (message.param.oneofKind === "archive")
|
||||
JobArchiveNextParam.internalBinaryWrite(message.param.archive, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||
/* job_restore.JobRestoreNextParam restore = 2; */
|
||||
JobArchiveDispatchParam.internalBinaryWrite(message.param.archive, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||
/* job_restore.JobRestoreDispatchParam restore = 2; */
|
||||
if (message.param.oneofKind === "restore")
|
||||
JobRestoreNextParam.internalBinaryWrite(message.param.restore, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
|
||||
JobRestoreDispatchParam.internalBinaryWrite(message.param.restore, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
@@ -479,9 +479,9 @@ class JobNextParam$Type extends MessageType<JobNextParam> {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message job.JobNextParam
|
||||
* @generated MessageType for protobuf message job.JobDispatchParam
|
||||
*/
|
||||
export const JobNextParam = new JobNextParam$Type();
|
||||
export const JobDispatchParam = new JobDispatchParam$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class CreatableJob$Type extends MessageType<CreatableJob> {
|
||||
constructor() {
|
||||
|
||||
@@ -23,9 +23,9 @@ export interface JobArchiveParam {
|
||||
sources: Source[];
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message job_archive.JobArchiveNextParam
|
||||
* @generated from protobuf message job_archive.JobArchiveDispatchParam
|
||||
*/
|
||||
export interface JobArchiveNextParam {
|
||||
export interface JobArchiveDispatchParam {
|
||||
/**
|
||||
* @generated from protobuf oneof: param
|
||||
*/
|
||||
@@ -189,22 +189,22 @@ class JobArchiveParam$Type extends MessageType<JobArchiveParam> {
|
||||
*/
|
||||
export const JobArchiveParam = new JobArchiveParam$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class JobArchiveNextParam$Type extends MessageType<JobArchiveNextParam> {
|
||||
class JobArchiveDispatchParam$Type extends MessageType<JobArchiveDispatchParam> {
|
||||
constructor() {
|
||||
super("job_archive.JobArchiveNextParam", [
|
||||
super("job_archive.JobArchiveDispatchParam", [
|
||||
{ no: 1, name: "wait_for_tape", kind: "message", oneof: "param", T: () => JobArchiveWaitForTapeParam },
|
||||
{ no: 2, name: "copying", kind: "message", oneof: "param", T: () => JobArchiveCopyingParam },
|
||||
{ no: 255, name: "finished", kind: "message", oneof: "param", T: () => JobArchiveFinishedParam }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<JobArchiveNextParam>): JobArchiveNextParam {
|
||||
create(value?: PartialMessage<JobArchiveDispatchParam>): JobArchiveDispatchParam {
|
||||
const message = { param: { oneofKind: undefined } };
|
||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<JobArchiveNextParam>(this, message, value);
|
||||
reflectionMergePartial<JobArchiveDispatchParam>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobArchiveNextParam): JobArchiveNextParam {
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobArchiveDispatchParam): JobArchiveDispatchParam {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
@@ -238,7 +238,7 @@ class JobArchiveNextParam$Type extends MessageType<JobArchiveNextParam> {
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: JobArchiveNextParam, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
internalBinaryWrite(message: JobArchiveDispatchParam, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* job_archive.JobArchiveWaitForTapeParam wait_for_tape = 1; */
|
||||
if (message.param.oneofKind === "waitForTape")
|
||||
JobArchiveWaitForTapeParam.internalBinaryWrite(message.param.waitForTape, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||
@@ -255,9 +255,9 @@ class JobArchiveNextParam$Type extends MessageType<JobArchiveNextParam> {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message job_archive.JobArchiveNextParam
|
||||
* @generated MessageType for protobuf message job_archive.JobArchiveDispatchParam
|
||||
*/
|
||||
export const JobArchiveNextParam = new JobArchiveNextParam$Type();
|
||||
export const JobArchiveDispatchParam = new JobArchiveDispatchParam$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class JobArchiveWaitForTapeParam$Type extends MessageType<JobArchiveWaitForTapeParam> {
|
||||
constructor() {
|
||||
|
||||
@@ -22,9 +22,9 @@ export interface JobRestoreParam {
|
||||
fileIds: bigint[];
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message job_restore.JobRestoreNextParam
|
||||
* @generated from protobuf message job_restore.JobRestoreDispatchParam
|
||||
*/
|
||||
export interface JobRestoreNextParam {
|
||||
export interface JobRestoreDispatchParam {
|
||||
/**
|
||||
* @generated from protobuf oneof: param
|
||||
*/
|
||||
@@ -246,22 +246,22 @@ class JobRestoreParam$Type extends MessageType<JobRestoreParam> {
|
||||
*/
|
||||
export const JobRestoreParam = new JobRestoreParam$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class JobRestoreNextParam$Type extends MessageType<JobRestoreNextParam> {
|
||||
class JobRestoreDispatchParam$Type extends MessageType<JobRestoreDispatchParam> {
|
||||
constructor() {
|
||||
super("job_restore.JobRestoreNextParam", [
|
||||
super("job_restore.JobRestoreDispatchParam", [
|
||||
{ no: 1, name: "wait_for_tape", kind: "message", oneof: "param", T: () => JobRestoreWaitForTapeParam },
|
||||
{ no: 2, name: "copying", kind: "message", oneof: "param", T: () => JobRestoreCopyingParam },
|
||||
{ no: 255, name: "finished", kind: "message", oneof: "param", T: () => JobRestoreFinishedParam }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<JobRestoreNextParam>): JobRestoreNextParam {
|
||||
create(value?: PartialMessage<JobRestoreDispatchParam>): JobRestoreDispatchParam {
|
||||
const message = { param: { oneofKind: undefined } };
|
||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<JobRestoreNextParam>(this, message, value);
|
||||
reflectionMergePartial<JobRestoreDispatchParam>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobRestoreNextParam): JobRestoreNextParam {
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobRestoreDispatchParam): JobRestoreDispatchParam {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
@@ -295,7 +295,7 @@ class JobRestoreNextParam$Type extends MessageType<JobRestoreNextParam> {
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: JobRestoreNextParam, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
internalBinaryWrite(message: JobRestoreDispatchParam, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* job_restore.JobRestoreWaitForTapeParam wait_for_tape = 1; */
|
||||
if (message.param.oneofKind === "waitForTape")
|
||||
JobRestoreWaitForTapeParam.internalBinaryWrite(message.param.waitForTape, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||
@@ -312,9 +312,9 @@ class JobRestoreNextParam$Type extends MessageType<JobRestoreNextParam> {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message job_restore.JobRestoreNextParam
|
||||
* @generated MessageType for protobuf message job_restore.JobRestoreDispatchParam
|
||||
*/
|
||||
export const JobRestoreNextParam = new JobRestoreNextParam$Type();
|
||||
export const JobRestoreDispatchParam = new JobRestoreDispatchParam$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class JobRestoreWaitForTapeParam$Type extends MessageType<JobRestoreWaitForTapeParam> {
|
||||
constructor() {
|
||||
|
||||
@@ -16,10 +16,12 @@ import type { JobGetLogReply } from "./service";
|
||||
import type { JobGetLogRequest } from "./service";
|
||||
import type { JobDisplayReply } from "./service";
|
||||
import type { JobDisplayRequest } from "./service";
|
||||
import type { JobNextReply } from "./service";
|
||||
import type { JobNextRequest } from "./service";
|
||||
import type { JobDispatchReply } from "./service";
|
||||
import type { JobDispatchRequest } from "./service";
|
||||
import type { JobDeleteReply } from "./service";
|
||||
import type { JobDeleteRequest } from "./service";
|
||||
import type { JobEditStateReply } from "./service";
|
||||
import type { JobEditStateRequest } from "./service";
|
||||
import type { JobCreateReply } from "./service";
|
||||
import type { JobCreateRequest } from "./service";
|
||||
import type { JobListReply } from "./service";
|
||||
@@ -87,14 +89,18 @@ export interface IServiceClient {
|
||||
* @generated from protobuf rpc: JobCreate(service.JobCreateRequest) returns (service.JobCreateReply);
|
||||
*/
|
||||
jobCreate(input: JobCreateRequest, options?: RpcOptions): UnaryCall<JobCreateRequest, JobCreateReply>;
|
||||
/**
|
||||
* @generated from protobuf rpc: JobEditState(service.JobEditStateRequest) returns (service.JobEditStateReply);
|
||||
*/
|
||||
jobEditState(input: JobEditStateRequest, options?: RpcOptions): UnaryCall<JobEditStateRequest, JobEditStateReply>;
|
||||
/**
|
||||
* @generated from protobuf rpc: JobDelete(service.JobDeleteRequest) returns (service.JobDeleteReply);
|
||||
*/
|
||||
jobDelete(input: JobDeleteRequest, options?: RpcOptions): UnaryCall<JobDeleteRequest, JobDeleteReply>;
|
||||
/**
|
||||
* @generated from protobuf rpc: JobNext(service.JobNextRequest) returns (service.JobNextReply);
|
||||
* @generated from protobuf rpc: JobDispatch(service.JobDispatchRequest) returns (service.JobDispatchReply);
|
||||
*/
|
||||
jobNext(input: JobNextRequest, options?: RpcOptions): UnaryCall<JobNextRequest, JobNextReply>;
|
||||
jobDispatch(input: JobDispatchRequest, options?: RpcOptions): UnaryCall<JobDispatchRequest, JobDispatchReply>;
|
||||
/**
|
||||
* @generated from protobuf rpc: JobDisplay(service.JobDisplayRequest) returns (service.JobDisplayReply);
|
||||
*/
|
||||
@@ -199,60 +205,67 @@ export class ServiceClient implements IServiceClient, ServiceInfo {
|
||||
const method = this.methods[9], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<JobCreateRequest, JobCreateReply>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: JobEditState(service.JobEditStateRequest) returns (service.JobEditStateReply);
|
||||
*/
|
||||
jobEditState(input: JobEditStateRequest, options?: RpcOptions): UnaryCall<JobEditStateRequest, JobEditStateReply> {
|
||||
const method = this.methods[10], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<JobEditStateRequest, JobEditStateReply>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: JobDelete(service.JobDeleteRequest) returns (service.JobDeleteReply);
|
||||
*/
|
||||
jobDelete(input: JobDeleteRequest, options?: RpcOptions): UnaryCall<JobDeleteRequest, JobDeleteReply> {
|
||||
const method = this.methods[10], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[11], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<JobDeleteRequest, JobDeleteReply>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: JobNext(service.JobNextRequest) returns (service.JobNextReply);
|
||||
* @generated from protobuf rpc: JobDispatch(service.JobDispatchRequest) returns (service.JobDispatchReply);
|
||||
*/
|
||||
jobNext(input: JobNextRequest, options?: RpcOptions): UnaryCall<JobNextRequest, JobNextReply> {
|
||||
const method = this.methods[11], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<JobNextRequest, JobNextReply>("unary", this._transport, method, opt, input);
|
||||
jobDispatch(input: JobDispatchRequest, options?: RpcOptions): UnaryCall<JobDispatchRequest, JobDispatchReply> {
|
||||
const method = this.methods[12], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<JobDispatchRequest, JobDispatchReply>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: JobDisplay(service.JobDisplayRequest) returns (service.JobDisplayReply);
|
||||
*/
|
||||
jobDisplay(input: JobDisplayRequest, options?: RpcOptions): UnaryCall<JobDisplayRequest, JobDisplayReply> {
|
||||
const method = this.methods[12], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[13], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<JobDisplayRequest, JobDisplayReply>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: JobGetLog(service.JobGetLogRequest) returns (service.JobGetLogReply);
|
||||
*/
|
||||
jobGetLog(input: JobGetLogRequest, options?: RpcOptions): UnaryCall<JobGetLogRequest, JobGetLogReply> {
|
||||
const method = this.methods[13], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[14], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<JobGetLogRequest, JobGetLogReply>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: SourceList(service.SourceListRequest) returns (service.SourceListReply);
|
||||
*/
|
||||
sourceList(input: SourceListRequest, options?: RpcOptions): UnaryCall<SourceListRequest, SourceListReply> {
|
||||
const method = this.methods[14], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[15], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SourceListRequest, SourceListReply>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: DeviceList(service.DeviceListRequest) returns (service.DeviceListReply);
|
||||
*/
|
||||
deviceList(input: DeviceListRequest, options?: RpcOptions): UnaryCall<DeviceListRequest, DeviceListReply> {
|
||||
const method = this.methods[15], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[16], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<DeviceListRequest, DeviceListReply>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: LibraryExport(service.LibraryExportRequest) returns (service.LibraryExportReply);
|
||||
*/
|
||||
libraryExport(input: LibraryExportRequest, options?: RpcOptions): UnaryCall<LibraryExportRequest, LibraryExportReply> {
|
||||
const method = this.methods[16], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[17], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<LibraryExportRequest, LibraryExportReply>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: LibraryTrim(service.LibraryTrimRequest) returns (service.LibraryTrimReply);
|
||||
*/
|
||||
libraryTrim(input: LibraryTrimRequest, options?: RpcOptions): UnaryCall<LibraryTrimRequest, LibraryTrimReply> {
|
||||
const method = this.methods[17], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[18], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<LibraryTrimRequest, LibraryTrimReply>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
}
|
||||
|
||||
+142
-55
@@ -15,7 +15,9 @@ import { MessageType } from "@protobuf-ts/runtime";
|
||||
import { LibraryEntityType } from "./library_entity_type";
|
||||
import { SourceFile } from "./source";
|
||||
import { JobDisplay } from "./job";
|
||||
import { JobNextParam } from "./job";
|
||||
import { JobDispatchParam } from "./job";
|
||||
import { JobState } from "./job";
|
||||
import { JobStatus } from "./job";
|
||||
import { CreatableJob } from "./job";
|
||||
import { Job } from "./job";
|
||||
import { JobRecentlyUpdateFilter } from "./job";
|
||||
@@ -269,6 +271,28 @@ export interface JobCreateReply {
|
||||
*/
|
||||
job?: Job;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message service.JobEditStateRequest
|
||||
*/
|
||||
export interface JobEditStateRequest {
|
||||
/**
|
||||
* @generated from protobuf field: int64 id = 1;
|
||||
*/
|
||||
id: bigint;
|
||||
/**
|
||||
* @generated from protobuf field: optional job.JobStatus status = 2;
|
||||
*/
|
||||
status?: JobStatus;
|
||||
/**
|
||||
* @generated from protobuf field: optional job.JobState state = 3;
|
||||
*/
|
||||
state?: JobState;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message service.JobEditStateReply
|
||||
*/
|
||||
export interface JobEditStateReply {
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message service.JobDeleteRequest
|
||||
*/
|
||||
@@ -284,26 +308,22 @@ export interface JobDeleteRequest {
|
||||
export interface JobDeleteReply {
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message service.JobNextRequest
|
||||
* @generated from protobuf message service.JobDispatchRequest
|
||||
*/
|
||||
export interface JobNextRequest {
|
||||
export interface JobDispatchRequest {
|
||||
/**
|
||||
* @generated from protobuf field: int64 id = 1;
|
||||
*/
|
||||
id: bigint;
|
||||
/**
|
||||
* @generated from protobuf field: job.JobNextParam param = 2;
|
||||
* @generated from protobuf field: job.JobDispatchParam param = 2;
|
||||
*/
|
||||
param?: JobNextParam;
|
||||
param?: JobDispatchParam;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message service.JobNextReply
|
||||
* @generated from protobuf message service.JobDispatchReply
|
||||
*/
|
||||
export interface JobNextReply {
|
||||
/**
|
||||
* @generated from protobuf field: job.Job job = 1;
|
||||
*/
|
||||
job?: Job;
|
||||
export interface JobDispatchReply {
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message service.JobDisplayRequest
|
||||
@@ -1521,6 +1541,93 @@ class JobCreateReply$Type extends MessageType<JobCreateReply> {
|
||||
*/
|
||||
export const JobCreateReply = new JobCreateReply$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class JobEditStateRequest$Type extends MessageType<JobEditStateRequest> {
|
||||
constructor() {
|
||||
super("service.JobEditStateRequest", [
|
||||
{ no: 1, name: "id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ },
|
||||
{ no: 2, name: "status", kind: "enum", opt: true, T: () => ["job.JobStatus", JobStatus] },
|
||||
{ no: 3, name: "state", kind: "message", T: () => JobState }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<JobEditStateRequest>): JobEditStateRequest {
|
||||
const message = { id: 0n };
|
||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<JobEditStateRequest>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobEditStateRequest): JobEditStateRequest {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* int64 id */ 1:
|
||||
message.id = reader.int64().toBigInt();
|
||||
break;
|
||||
case /* optional job.JobStatus status */ 2:
|
||||
message.status = reader.int32();
|
||||
break;
|
||||
case /* optional job.JobState state */ 3:
|
||||
message.state = JobState.internalBinaryRead(reader, reader.uint32(), options, message.state);
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: JobEditStateRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* int64 id = 1; */
|
||||
if (message.id !== 0n)
|
||||
writer.tag(1, WireType.Varint).int64(message.id);
|
||||
/* optional job.JobStatus status = 2; */
|
||||
if (message.status !== undefined)
|
||||
writer.tag(2, WireType.Varint).int32(message.status);
|
||||
/* optional job.JobState state = 3; */
|
||||
if (message.state)
|
||||
JobState.internalBinaryWrite(message.state, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message service.JobEditStateRequest
|
||||
*/
|
||||
export const JobEditStateRequest = new JobEditStateRequest$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class JobEditStateReply$Type extends MessageType<JobEditStateReply> {
|
||||
constructor() {
|
||||
super("service.JobEditStateReply", []);
|
||||
}
|
||||
create(value?: PartialMessage<JobEditStateReply>): JobEditStateReply {
|
||||
const message = {};
|
||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<JobEditStateReply>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobEditStateReply): JobEditStateReply {
|
||||
return target ?? this.create();
|
||||
}
|
||||
internalBinaryWrite(message: JobEditStateReply, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message service.JobEditStateReply
|
||||
*/
|
||||
export const JobEditStateReply = new JobEditStateReply$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class JobDeleteRequest$Type extends MessageType<JobDeleteRequest> {
|
||||
constructor() {
|
||||
super("service.JobDeleteRequest", [
|
||||
@@ -1602,21 +1709,21 @@ class JobDeleteReply$Type extends MessageType<JobDeleteReply> {
|
||||
*/
|
||||
export const JobDeleteReply = new JobDeleteReply$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class JobNextRequest$Type extends MessageType<JobNextRequest> {
|
||||
class JobDispatchRequest$Type extends MessageType<JobDispatchRequest> {
|
||||
constructor() {
|
||||
super("service.JobNextRequest", [
|
||||
super("service.JobDispatchRequest", [
|
||||
{ no: 1, name: "id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ },
|
||||
{ no: 2, name: "param", kind: "message", T: () => JobNextParam }
|
||||
{ no: 2, name: "param", kind: "message", T: () => JobDispatchParam }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<JobNextRequest>): JobNextRequest {
|
||||
create(value?: PartialMessage<JobDispatchRequest>): JobDispatchRequest {
|
||||
const message = { id: 0n };
|
||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<JobNextRequest>(this, message, value);
|
||||
reflectionMergePartial<JobDispatchRequest>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobNextRequest): JobNextRequest {
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobDispatchRequest): JobDispatchRequest {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
@@ -1624,8 +1731,8 @@ class JobNextRequest$Type extends MessageType<JobNextRequest> {
|
||||
case /* int64 id */ 1:
|
||||
message.id = reader.int64().toBigInt();
|
||||
break;
|
||||
case /* job.JobNextParam param */ 2:
|
||||
message.param = JobNextParam.internalBinaryRead(reader, reader.uint32(), options, message.param);
|
||||
case /* job.JobDispatchParam param */ 2:
|
||||
message.param = JobDispatchParam.internalBinaryRead(reader, reader.uint32(), options, message.param);
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
@@ -1638,13 +1745,13 @@ class JobNextRequest$Type extends MessageType<JobNextRequest> {
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: JobNextRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
internalBinaryWrite(message: JobDispatchRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* int64 id = 1; */
|
||||
if (message.id !== 0n)
|
||||
writer.tag(1, WireType.Varint).int64(message.id);
|
||||
/* job.JobNextParam param = 2; */
|
||||
/* job.JobDispatchParam param = 2; */
|
||||
if (message.param)
|
||||
JobNextParam.internalBinaryWrite(message.param, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
|
||||
JobDispatchParam.internalBinaryWrite(message.param, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
@@ -1652,46 +1759,25 @@ class JobNextRequest$Type extends MessageType<JobNextRequest> {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message service.JobNextRequest
|
||||
* @generated MessageType for protobuf message service.JobDispatchRequest
|
||||
*/
|
||||
export const JobNextRequest = new JobNextRequest$Type();
|
||||
export const JobDispatchRequest = new JobDispatchRequest$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class JobNextReply$Type extends MessageType<JobNextReply> {
|
||||
class JobDispatchReply$Type extends MessageType<JobDispatchReply> {
|
||||
constructor() {
|
||||
super("service.JobNextReply", [
|
||||
{ no: 1, name: "job", kind: "message", T: () => Job }
|
||||
]);
|
||||
super("service.JobDispatchReply", []);
|
||||
}
|
||||
create(value?: PartialMessage<JobNextReply>): JobNextReply {
|
||||
create(value?: PartialMessage<JobDispatchReply>): JobDispatchReply {
|
||||
const message = {};
|
||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<JobNextReply>(this, message, value);
|
||||
reflectionMergePartial<JobDispatchReply>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobNextReply): JobNextReply {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* job.Job job */ 1:
|
||||
message.job = Job.internalBinaryRead(reader, reader.uint32(), options, message.job);
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: JobDispatchReply): JobDispatchReply {
|
||||
return target ?? this.create();
|
||||
}
|
||||
internalBinaryWrite(message: JobNextReply, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* job.Job job = 1; */
|
||||
if (message.job)
|
||||
Job.internalBinaryWrite(message.job, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||
internalBinaryWrite(message: JobDispatchReply, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
@@ -1699,9 +1785,9 @@ class JobNextReply$Type extends MessageType<JobNextReply> {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message service.JobNextReply
|
||||
* @generated MessageType for protobuf message service.JobDispatchReply
|
||||
*/
|
||||
export const JobNextReply = new JobNextReply$Type();
|
||||
export const JobDispatchReply = new JobDispatchReply$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class JobDisplayRequest$Type extends MessageType<JobDisplayRequest> {
|
||||
constructor() {
|
||||
@@ -2281,8 +2367,9 @@ export const Service = new ServiceType("service.Service", [
|
||||
{ name: "TapeGetPositions", options: {}, I: TapeGetPositionsRequest, O: TapeGetPositionsReply },
|
||||
{ name: "JobList", options: {}, I: JobListRequest, O: JobListReply },
|
||||
{ name: "JobCreate", options: {}, I: JobCreateRequest, O: JobCreateReply },
|
||||
{ name: "JobEditState", options: {}, I: JobEditStateRequest, O: JobEditStateReply },
|
||||
{ name: "JobDelete", options: {}, I: JobDeleteRequest, O: JobDeleteReply },
|
||||
{ name: "JobNext", options: {}, I: JobNextRequest, O: JobNextReply },
|
||||
{ name: "JobDispatch", options: {}, I: JobDispatchRequest, O: JobDispatchReply },
|
||||
{ name: "JobDisplay", options: {}, I: JobDisplayRequest, O: JobDisplayReply },
|
||||
{ name: "JobGetLog", options: {}, I: JobGetLogRequest, O: JobGetLogReply },
|
||||
{ name: "SourceList", options: {}, I: SourceListRequest, O: SourceListReply },
|
||||
|
||||
+33
-619
@@ -1,56 +1,32 @@
|
||||
import { Fragment, ChangeEvent } from "react";
|
||||
import { useState, useRef, useEffect, useMemo, useCallback, FC } from "react";
|
||||
import { createContext, useContext } from "react";
|
||||
import { assert } from "@protobuf-ts/runtime";
|
||||
import format from "format-duration";
|
||||
import { useState, useRef, useEffect, useCallback } from "react";
|
||||
import { createContext } from "react";
|
||||
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Box from "@mui/material/Box";
|
||||
import List from "@mui/material/List";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
import Card from "@mui/material/Card";
|
||||
import CardActions from "@mui/material/CardActions";
|
||||
import CardContent from "@mui/material/CardContent";
|
||||
|
||||
import Button from "@mui/material/Button";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Chip, { ChipProps } from "@mui/material/Chip";
|
||||
import Stack from "@mui/material/Stack";
|
||||
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
import Divider from "@mui/material/Divider";
|
||||
|
||||
import { TreeView, TreeItem } from "@mui/x-tree-view";
|
||||
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
|
||||
import { cli, fileBase, JOB_STATUS_VISIBLE } from "../api";
|
||||
import { sleep } from "../tools";
|
||||
import { Job, JobDisplay, JobListRequest, JobNextRequest, JobStatus, CopyStatus, LibraryEntityType, JobDeleteRequest } from "../entity";
|
||||
import { Job, JobDisplay, JobListRequest, JobStatus, LibraryEntityType } from "../entity";
|
||||
|
||||
import { JobArchiveCopyingParam, JobArchiveStep, JobArchiveDisplay, JobArchiveState } from "../entity";
|
||||
import { SourceState } from "../entity";
|
||||
import { download } from "../tools";
|
||||
|
||||
import { JobRestoreCopyingParam, JobRestoreStep, JobRestoreDisplay, JobRestoreState } from "../entity";
|
||||
import { RestoreTape } from "../entity";
|
||||
|
||||
import { formatFilesize, download } from "../tools";
|
||||
import { Nullable } from "tsdef";
|
||||
import { JobCard } from "../components/job-card";
|
||||
import { ArchiveCard } from "../components/job-archive";
|
||||
import { RestoreCard } from "../components/job-restore";
|
||||
|
||||
export const JobsType = "jobs";
|
||||
type DisplayableJob = Job & Partial<JobDisplay>;
|
||||
export const RefreshContext = createContext<() => Promise<void>>(async () => {});
|
||||
|
||||
const RefreshContext = createContext<() => Promise<void>>(async () => {});
|
||||
type DisplayableJob = Job & Partial<JobDisplay>;
|
||||
|
||||
export const JobsBrowser = () => {
|
||||
const [jobs, setJobs] = useState<DisplayableJob[] | null>(null);
|
||||
@@ -58,7 +34,7 @@ export const JobsBrowser = () => {
|
||||
|
||||
const refresh = useCallback(
|
||||
async (refresh?: boolean) => {
|
||||
const results = await (async () => {
|
||||
const [results, updated] = await (async () => {
|
||||
const req: JobListRequest = refresh
|
||||
? { param: { oneofKind: "list", list: {} } }
|
||||
: { param: { oneofKind: "recentlyUpdate", recentlyUpdate: { updateSinceNs: latestUpdateTimeNs } } };
|
||||
@@ -66,9 +42,9 @@ export const JobsBrowser = () => {
|
||||
const reply = await cli.jobList(req).response;
|
||||
if (reply.jobs.length === 0) {
|
||||
if (refresh) {
|
||||
return [];
|
||||
return [[], true];
|
||||
}
|
||||
return Array.from(jobs || []);
|
||||
return [Array.from(jobs || []), false];
|
||||
}
|
||||
|
||||
const latest = reply.jobs.reduce((latest, job) => {
|
||||
@@ -93,25 +69,28 @@ export const JobsBrowser = () => {
|
||||
|
||||
results.push(job);
|
||||
}
|
||||
return results.filter((job) => job && job.status < JOB_STATUS_VISIBLE).sort((a, b) => Number(b.createTimeNs - a.createTimeNs));
|
||||
return [results.filter((job) => job && job.status < JOB_STATUS_VISIBLE).sort((a, b) => Number(b.createTimeNs - a.createTimeNs)), true];
|
||||
})();
|
||||
|
||||
const displays = new Map<BigInt, JobDisplay>();
|
||||
const processingJobs = results.filter((job) => job.status === JobStatus.PROCESSING);
|
||||
if (processingJobs.length === 0) {
|
||||
if (updated) {
|
||||
setJobs(results);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (const reply of await Promise.all(
|
||||
results
|
||||
.filter((job) => job.status === JobStatus.PROCESSING)
|
||||
.map((job) => cli.jobDisplay({ id: job.id }).response.then((reply) => ({ ...reply, jobID: job.id }))),
|
||||
processingJobs.map((job) => cli.jobDisplay({ id: job.id }).response.then((reply) => ({ ...reply, jobID: job.id }))),
|
||||
)) {
|
||||
if (!reply.display) {
|
||||
continue;
|
||||
}
|
||||
|
||||
displays.set(reply.jobID, reply.display);
|
||||
}
|
||||
|
||||
const targets = results.map((job) => ({ ...job, ...displays.get(job.id) }));
|
||||
console.log("refresh jobs list, set jobs, jobs=", targets);
|
||||
setJobs(targets);
|
||||
setJobs(results.map((job) => ({ ...job, ...displays.get(job.id) })));
|
||||
},
|
||||
[jobs, setJobs, latestUpdateTimeNs, setLatestUpdateTimeNs],
|
||||
);
|
||||
@@ -133,6 +112,7 @@ export const JobsBrowser = () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => console.log("jobs changed,", jobs), [jobs]);
|
||||
|
||||
return (
|
||||
<RefreshContext.Provider value={refresh}>
|
||||
@@ -153,20 +133,19 @@ export const JobsBrowser = () => {
|
||||
// </ListSubheader>
|
||||
// }
|
||||
>
|
||||
{/* <NewArchiveDialog refresh={refresh} /> */}
|
||||
<ListItemButton
|
||||
onClick={async () => {
|
||||
const resp = await cli.libraryExport({ types: [LibraryEntityType.FILE, LibraryEntityType.TAPE, LibraryEntityType.POSITION] }).response;
|
||||
download(resp.json, "database.json", "application/json");
|
||||
download(resp.json, "library.json", "application/json");
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Export Database" />
|
||||
<ListItemText primary="Export Library" />
|
||||
</ListItemButton>
|
||||
<ImportDatabaseDialog />
|
||||
</List>
|
||||
</Grid>
|
||||
<Grid className="browser" item xs={10}>
|
||||
<div className="job-list">{jobs ? jobs.map((job) => <GetJobCard job={job} key={job.id.toString()} refresh={refresh} />) : <LinearProgress />}</div>
|
||||
<div className="job-list">{jobs ? jobs.map((job) => <GetJobCard job={job} key={job.id.toString()} />) : <LinearProgress />}</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
@@ -211,11 +190,11 @@ const ImportDatabaseDialog = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<ListItemButton onClick={handleClickOpen}>
|
||||
<ListItemText primary="Import Database" />
|
||||
<ListItemText primary="Import Library" />
|
||||
</ListItemButton>
|
||||
{open && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"sm"} fullWidth>
|
||||
<DialogTitle>Load Tape</DialogTitle>
|
||||
<DialogTitle>Upload Library JSON</DialogTitle>
|
||||
<DialogContent>
|
||||
<Button variant="contained" component="label">
|
||||
Upload File
|
||||
@@ -233,7 +212,7 @@ const ImportDatabaseDialog = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const GetJobCard = ({ job, refresh }: { job: DisplayableJob; refresh: () => Promise<void> }): JSX.Element => {
|
||||
const GetJobCard = ({ job }: { job: DisplayableJob }): JSX.Element => {
|
||||
if (!job.state) {
|
||||
return <JobCard job={job} />;
|
||||
}
|
||||
@@ -241,575 +220,10 @@ const GetJobCard = ({ job, refresh }: { job: DisplayableJob; refresh: () => Prom
|
||||
const type = job.state?.state.oneofKind;
|
||||
switch (type) {
|
||||
case "archive":
|
||||
return (
|
||||
<ArchiveCard job={job} refresh={refresh} state={job.state.state.archive} display={job.display?.oneofKind === "archive" ? job.display.archive : null} />
|
||||
);
|
||||
return <ArchiveCard job={job} state={job.state.state.archive} display={job.display?.oneofKind === "archive" ? job.display.archive : null} />;
|
||||
case "restore":
|
||||
return (
|
||||
<RestoreCard job={job} refresh={refresh} state={job.state.state.restore} display={job.display?.oneofKind === "restore" ? job.display.restore : null} />
|
||||
);
|
||||
return <RestoreCard job={job} state={job.state.state.restore} display={job.display?.oneofKind === "restore" ? job.display.restore : null} />;
|
||||
default:
|
||||
return <JobCard job={job} />;
|
||||
}
|
||||
};
|
||||
|
||||
const ArchiveViewFilesDialog = ({ sources }: { sources: SourceState[] }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button size="small" onClick={handleClickOpen}>
|
||||
View Files
|
||||
</Button>
|
||||
{open && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"lg"} fullWidth scroll="paper" sx={{ height: "100%" }} className="view-log-dialog">
|
||||
<DialogTitle>View Files</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{sources.map((src) => {
|
||||
if (!src.source) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ListItemText
|
||||
primary={src.source.base + src.source.path.join("/")}
|
||||
secondary={`Size: ${formatFilesize(src.size)} Status: ${CopyStatus[src.status]}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const ArchiveCard = ({
|
||||
job,
|
||||
state,
|
||||
display,
|
||||
refresh,
|
||||
}: {
|
||||
job: Job;
|
||||
state: JobArchiveState;
|
||||
display: JobArchiveDisplay | null;
|
||||
refresh: () => Promise<void>;
|
||||
}): JSX.Element => {
|
||||
const [fields, progress] = useMemo(() => {
|
||||
const totalFiles = state.sources.length;
|
||||
let submitedFiles = 0,
|
||||
submitedBytes = 0,
|
||||
totalBytes = 0;
|
||||
for (const file of state.sources) {
|
||||
totalBytes += Number(file.size);
|
||||
if (file.status !== CopyStatus.SUBMITED) {
|
||||
continue;
|
||||
}
|
||||
submitedFiles++;
|
||||
submitedBytes += Number(file.size);
|
||||
}
|
||||
|
||||
const copiedFiles = submitedFiles + Number(display?.copiedFiles || 0n);
|
||||
const copiedBytes = submitedBytes + Number(display?.copiedBytes || 0n);
|
||||
const avgSpeed = (() => {
|
||||
if (!display || !display.copiedBytes || !display.startTime) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
const duration = Date.now() / 1000 - Number(display.startTime);
|
||||
if (duration <= 0) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
return Number(display.copiedBytes) / duration;
|
||||
})();
|
||||
|
||||
const progress = (totalBytes > 0 ? copiedBytes / totalBytes : 1) * 100;
|
||||
const fields = [
|
||||
{ name: "Current Step", value: JobArchiveStep[state.step] },
|
||||
{ name: "Current Speed", value: display?.speed ? `${formatFilesize(display?.speed)}/s` : "--" },
|
||||
{ name: "Average Speed", value: !isNaN(avgSpeed) ? `${formatFilesize(avgSpeed)}/s` : "--" },
|
||||
{ name: "Estimated Time", value: !isNaN(avgSpeed) ? format(((totalBytes - copiedBytes) * 1000) / avgSpeed) : "--" },
|
||||
{ name: "Copied Files", value: copiedFiles },
|
||||
{ name: "Copied Bytes", value: formatFilesize(copiedBytes) },
|
||||
{ name: "Submited Files", value: submitedFiles },
|
||||
{ name: "Submited Bytes", value: formatFilesize(submitedBytes) },
|
||||
{ name: "Total Files", value: totalFiles },
|
||||
{ name: "Total Bytes", value: formatFilesize(totalBytes) },
|
||||
];
|
||||
|
||||
return [fields, progress];
|
||||
}, [state, display]);
|
||||
|
||||
return (
|
||||
<JobCard
|
||||
job={job}
|
||||
detail={
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{ paddingTop: "1em" }}>
|
||||
<LinearProgress variant="determinate" value={progress} />
|
||||
</Box>
|
||||
</Grid>
|
||||
{fields.map((field, idx) => (
|
||||
<Grid item xs={12} md={3} key={idx}>
|
||||
<Typography variant="body1">
|
||||
<b>{field.name}</b>: {field.value}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
}
|
||||
buttons={
|
||||
<Fragment>
|
||||
{state.step === JobArchiveStep.WAIT_FOR_TAPE && <NewTapeDialog job={job} refresh={refresh} />}
|
||||
<ViewLogDialog jobID={job.id} />
|
||||
<ArchiveViewFilesDialog sources={state.sources} />
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const RestoreViewFilesDialog = ({ tapes }: { tapes: RestoreTape[] }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button size="small" onClick={handleClickOpen}>
|
||||
View Files
|
||||
</Button>
|
||||
{open && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"lg"} fullWidth scroll="paper" sx={{ height: "100%" }} className="view-log-dialog">
|
||||
<DialogTitle>View Files</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<TreeView defaultCollapseIcon={<ExpandMoreIcon />} defaultExpandIcon={<ChevronRightIcon />}>
|
||||
{tapes.map((tape) => {
|
||||
if (!tape.files) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TreeItem label={tape.barcode} nodeId={`tape-${tape.tapeId}`}>
|
||||
{tape.files.map((file) => (
|
||||
<TreeItem
|
||||
label={
|
||||
<pre style={{ margin: 0 }}>
|
||||
{file.tapePath} <b>{CopyStatus[file.status]}</b>
|
||||
</pre>
|
||||
}
|
||||
nodeId={`file-${file.positionId}`}
|
||||
/>
|
||||
))}
|
||||
</TreeItem>
|
||||
);
|
||||
})}
|
||||
</TreeView>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const tapeStatusToColor = (status: CopyStatus): ChipProps["color"] => {
|
||||
switch (status) {
|
||||
case CopyStatus.DRAFT:
|
||||
return "primary";
|
||||
case CopyStatus.PENDING:
|
||||
return "primary";
|
||||
case CopyStatus.RUNNING:
|
||||
return "secondary";
|
||||
case CopyStatus.STAGED:
|
||||
return "warning";
|
||||
case CopyStatus.SUBMITED:
|
||||
return "success";
|
||||
case CopyStatus.FAILED:
|
||||
return "error";
|
||||
default:
|
||||
return "default";
|
||||
}
|
||||
};
|
||||
|
||||
const RestoreCard = ({
|
||||
job,
|
||||
state,
|
||||
display,
|
||||
refresh,
|
||||
}: {
|
||||
job: Job;
|
||||
state: JobRestoreState;
|
||||
display: JobRestoreDisplay | null;
|
||||
refresh: () => Promise<void>;
|
||||
}): JSX.Element => {
|
||||
const [fields, progress] = useMemo(() => {
|
||||
const totalFiles = state.tapes.reduce((count, tape) => count + tape.files.length, 0);
|
||||
let successFiles = 0,
|
||||
successBytes = 0,
|
||||
copiedFiles = Number(display?.copiedFiles || 0n),
|
||||
copiedBytes = Number(display?.copiedBytes || 0n),
|
||||
totalBytes = 0;
|
||||
for (const tape of state.tapes) {
|
||||
for (const file of tape.files) {
|
||||
totalBytes += Number(file.size);
|
||||
|
||||
if (file.status === CopyStatus.SUBMITED || file.status === CopyStatus.STAGED) {
|
||||
successFiles++;
|
||||
successBytes += Number(file.size);
|
||||
}
|
||||
|
||||
if (file.status === CopyStatus.SUBMITED) {
|
||||
copiedFiles++;
|
||||
copiedBytes += Number(file.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const avgSpeed = (() => {
|
||||
if (!display || !display.copiedBytes || !display.startTime) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
const duration = Date.now() / 1000 - Number(display.startTime);
|
||||
if (duration <= 0) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
return Number(display.copiedBytes) / duration;
|
||||
})();
|
||||
|
||||
const progress = (totalBytes > 0 ? copiedBytes / totalBytes : 1) * 100;
|
||||
const fields = [
|
||||
{ name: "Current Step", value: JobArchiveStep[state.step] },
|
||||
{ name: "Current Speed", value: display?.speed ? `${formatFilesize(display?.speed)}/s` : "--" },
|
||||
{ name: "Average Speed", value: !isNaN(avgSpeed) ? `${formatFilesize(avgSpeed)}/s` : "--" },
|
||||
{ name: "Estimated Time", value: !isNaN(avgSpeed) ? format(((totalBytes - copiedBytes) * 1000) / avgSpeed) : "--" },
|
||||
{ name: "Copied Files", value: copiedFiles },
|
||||
{ name: "Copied Bytes", value: formatFilesize(copiedBytes) },
|
||||
{ name: "Success Files", value: successFiles },
|
||||
{ name: "Success Bytes", value: formatFilesize(successBytes) },
|
||||
{ name: "Total Files", value: totalFiles },
|
||||
{ name: "Total Bytes", value: formatFilesize(totalBytes) },
|
||||
];
|
||||
|
||||
return [fields, progress];
|
||||
}, [state, display]);
|
||||
|
||||
return (
|
||||
<JobCard
|
||||
job={job}
|
||||
detail={
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{ paddingTop: "1em" }}>
|
||||
<LinearProgress variant="determinate" value={progress} />
|
||||
</Box>
|
||||
</Grid>
|
||||
{fields.map((field, idx) => (
|
||||
<Grid item xs={12} md={3} key={idx}>
|
||||
<Typography variant="body1">
|
||||
<b>{field.name}</b>: {field.value}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
<Grid item xs={12} md={12}>
|
||||
<Stack direction="row" spacing={1}>
|
||||
{state.tapes.map((tape) => (
|
||||
<Chip label={`${tape.barcode}: ${CopyStatus[tape.status]}`} color={tapeStatusToColor(tape.status)} variant="outlined" key={`${tape.tapeId}`} />
|
||||
))}
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
}
|
||||
buttons={
|
||||
<Fragment>
|
||||
{state.step === JobRestoreStep.WAIT_FOR_TAPE && <LoadTapeDialog job={job} refresh={refresh} />}
|
||||
<ViewLogDialog jobID={job.id} />
|
||||
<RestoreViewFilesDialog tapes={state.tapes} />
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const NewTapeDialog = ({ job, refresh }: { job: Job; refresh: () => Promise<void> }) => {
|
||||
const [devices, setDevices] = useState<string[]>([]);
|
||||
const [param, setParam] = useState<JobArchiveCopyingParam | null>(null);
|
||||
const handleClickOpen = async () => {
|
||||
const reply = await cli.deviceList({}).response;
|
||||
setDevices(reply.devices);
|
||||
setParam(JobArchiveCopyingParam.create());
|
||||
};
|
||||
const handleClose = () => {
|
||||
setParam(null);
|
||||
setDevices([]);
|
||||
};
|
||||
const handleChange = (key: keyof JobArchiveCopyingParam) => (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (param === null) {
|
||||
return;
|
||||
}
|
||||
setParam({ ...param, [key]: event.target.value });
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
if (!param) {
|
||||
return;
|
||||
}
|
||||
|
||||
const trimedParam: JobArchiveCopyingParam = {
|
||||
device: param.device,
|
||||
barcode: param.barcode.toUpperCase(),
|
||||
name: param.name,
|
||||
};
|
||||
assert(trimedParam.barcode.length === 6);
|
||||
|
||||
const reply = await cli.jobNext(makeArchiveCopyingParam(job.id, trimedParam)).response;
|
||||
console.log("job next reply= ", reply);
|
||||
await refresh();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button size="small" onClick={handleClickOpen}>
|
||||
Load Tape
|
||||
</Button>
|
||||
{param && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"sm"} fullWidth>
|
||||
<DialogTitle>Load Tape</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>After load tape into tape drive, click 'Submit'</DialogContentText>
|
||||
<TextField select required margin="dense" label="Drive Device" fullWidth variant="standard" value={param.device} onChange={handleChange("device")}>
|
||||
{devices.map((device) => (
|
||||
<MenuItem key={device} value={device}>
|
||||
{device}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField required margin="dense" label="Tape Barcode" fullWidth variant="standard" value={param.barcode} onChange={handleChange("barcode")} />
|
||||
<TextField required margin="dense" label="Tape Name" fullWidth variant="standard" value={param.name} onChange={handleChange("name")} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button onClick={handleSubmit}>Submit</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadTapeDialog = ({ job, refresh }: { job: Job; refresh: () => Promise<void> }) => {
|
||||
const [devices, setDevices] = useState<string[] | null>(null);
|
||||
const [device, setDevice] = useState<string | null>(null);
|
||||
const handleClickOpen = async () => {
|
||||
const reply = await cli.deviceList({}).response;
|
||||
setDevices(reply.devices);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setDevices(null);
|
||||
setDevice(null);
|
||||
};
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setDevice(event.target.value);
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const trimedParam: JobRestoreCopyingParam = {
|
||||
device: device,
|
||||
};
|
||||
|
||||
const reply = await cli.jobNext(makeRestoreCopyingParam(job.id, trimedParam)).response;
|
||||
console.log("job next reply= ", reply);
|
||||
await refresh();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button size="small" onClick={handleClickOpen}>
|
||||
Load Tape
|
||||
</Button>
|
||||
{devices && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"sm"} fullWidth>
|
||||
<DialogTitle>Load Tape</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>After load tape into tape drive, click 'Submit'</DialogContentText>
|
||||
<TextField select required margin="dense" label="Drive Device" fullWidth variant="standard" value={device} onChange={handleChange}>
|
||||
{devices.map((device) => (
|
||||
<MenuItem key={device} value={device}>
|
||||
{device}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button onClick={handleSubmit}>Submit</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const ViewLogDialog = ({ jobID }: { jobID: bigint }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button size="small" onClick={handleClickOpen}>
|
||||
View Log
|
||||
</Button>
|
||||
{open && (
|
||||
<Dialog open={true} onClose={handleClose} maxWidth={"lg"} fullWidth scroll="paper" sx={{ height: "100%" }} className="view-log-dialog">
|
||||
<DialogTitle>View Log</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<LogConsole jobId={jobID} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const LogConsole = ({ jobId }: { jobId: bigint }) => {
|
||||
const [log, setLog] = useState<string>("");
|
||||
const [offset, setOffset] = useState(0n);
|
||||
const bottom = useRef(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
const reply = await cli.jobGetLog({ jobId, offset: offset }).response;
|
||||
setLog(log + new TextDecoder().decode(reply.logs));
|
||||
setOffset(reply.offset);
|
||||
}, [log, setLog, offset, setOffset, bottom]);
|
||||
const refreshRef = useRef(refresh);
|
||||
refreshRef.current = refresh;
|
||||
|
||||
useEffect(() => {
|
||||
var timer: NodeJS.Timeout;
|
||||
(async () => {
|
||||
await refreshRef.current();
|
||||
if (bottom.current) {
|
||||
const bottomElem = bottom.current as HTMLElement;
|
||||
await sleep(10);
|
||||
bottomElem.scrollIntoView(true);
|
||||
await sleep(10);
|
||||
bottomElem.parentElement?.scrollBy(0, 100);
|
||||
}
|
||||
|
||||
timer = setInterval(() => refreshRef.current(), 2000);
|
||||
})();
|
||||
|
||||
return () => {
|
||||
if (!timer) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, [refresh]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<pre>{log || "loading..."}</pre>
|
||||
<div ref={bottom} />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const DeleteJobButton = ({ jobID }: { jobID: bigint }) => {
|
||||
const refresh = useContext(RefreshContext);
|
||||
const deleteJob = useCallback(async () => {
|
||||
await cli.jobDelete(JobDeleteRequest.create({ ids: [jobID] }));
|
||||
await refresh();
|
||||
}, [jobID]);
|
||||
|
||||
return (
|
||||
<Button size="small" onClick={deleteJob} style={{ marginLeft: "auto", marginRight: 0 }}>
|
||||
Delete Job
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const JobCard = ({ job, detail, buttons }: { job: Job; detail?: JSX.Element; buttons?: JSX.Element }) => {
|
||||
return (
|
||||
<Card sx={{ textAlign: "left" }} className="job-detail">
|
||||
<CardContent>
|
||||
<Typography sx={{ fontSize: 14 }} color="text.secondary" gutterBottom>
|
||||
{`${JobStatus[job.status]}`}
|
||||
</Typography>
|
||||
<Typography variant="h5" component="div">{`Job ${job.id} - ${job.state?.state.oneofKind?.toUpperCase()}`}</Typography>
|
||||
{detail}
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardActions>
|
||||
{buttons}
|
||||
<DeleteJobButton jobID={job.id} />
|
||||
</CardActions>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
function makeArchiveCopyingParam(jobID: bigint, param: JobArchiveCopyingParam): JobNextRequest {
|
||||
return {
|
||||
id: jobID,
|
||||
param: {
|
||||
param: {
|
||||
oneofKind: "archive",
|
||||
archive: {
|
||||
param: {
|
||||
oneofKind: "copying",
|
||||
copying: param,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function makeRestoreCopyingParam(jobID: bigint, param: JobRestoreCopyingParam): JobNextRequest {
|
||||
return {
|
||||
id: jobID,
|
||||
param: {
|
||||
param: {
|
||||
oneofKind: "restore",
|
||||
restore: {
|
||||
param: {
|
||||
oneofKind: "copying",
|
||||
copying: param,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ func (l *Library) ListPositions(ctx context.Context, tapeID int64, prefix string
|
||||
return nil, fmt.Errorf("find position by file id fail, %w", r.Error)
|
||||
}
|
||||
|
||||
convertPath := tools.Cache(func(p string) string { return strings.ReplaceAll(p, "/", "\x00") })
|
||||
convertPath := tools.ThreadUnsafeCache(func(p string) string { return strings.ReplaceAll(p, "/", "\x00") })
|
||||
sort.Slice(positions, func(i int, j int) bool {
|
||||
return convertPath(positions[i].Path) < convertPath(positions[j].Path)
|
||||
})
|
||||
|
||||
+67
-1
@@ -1,6 +1,9 @@
|
||||
package tools
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func Cache[K comparable, V any](f func(in K) V) func(in K) V {
|
||||
cache := new(sync.Map)
|
||||
@@ -15,3 +18,66 @@ func Cache[K comparable, V any](f func(in K) V) func(in K) V {
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
func ThreadUnsafeCache[K comparable, V any](f func(in K) V) func(in K) V {
|
||||
cache := make(map[K]V, 32)
|
||||
return func(in K) V {
|
||||
cached, has := cache[in]
|
||||
if has {
|
||||
return cached
|
||||
}
|
||||
|
||||
out := f(in)
|
||||
cache[in] = out
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
type CacheOnce[K comparable, V any] struct {
|
||||
cached sync.Map
|
||||
getter func(context.Context, K) (V, error)
|
||||
}
|
||||
|
||||
type cacheOnceItem[V any] struct {
|
||||
once sync.Once
|
||||
value V
|
||||
}
|
||||
|
||||
func NewCacheOnce[K comparable, V any](getter func(context.Context, K) (V, error)) *CacheOnce[K, V] {
|
||||
return &CacheOnce[K, V]{getter: getter}
|
||||
}
|
||||
|
||||
func (c *CacheOnce[K, V]) Get(ctx context.Context, req K) (V, error) {
|
||||
if v, have := c.cached.Load(req); have {
|
||||
item, ok := v.(*cacheOnceItem[V])
|
||||
if ok {
|
||||
return c.get(ctx, item, req)
|
||||
}
|
||||
}
|
||||
|
||||
v, _ := c.cached.LoadOrStore(req, &cacheOnceItem[V]{})
|
||||
return c.get(ctx, v.(*cacheOnceItem[V]), req)
|
||||
}
|
||||
|
||||
func (c *CacheOnce[K, V]) Remove(req K) {
|
||||
c.cached.Delete(req)
|
||||
}
|
||||
|
||||
func (c *CacheOnce[K, V]) get(ctx context.Context, item *cacheOnceItem[V], key K) (V, error) {
|
||||
var err error
|
||||
item.once.Do(func() {
|
||||
v, e := c.getter(ctx, key)
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
item.value = v
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
var v V
|
||||
return v, err
|
||||
}
|
||||
|
||||
return item.value, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user