diff --git a/src/app/core/bitstream-checksum-data.service.ts b/src/app/core/bitstream-checksum-data.service.ts new file mode 100644 index 00000000000..94dfef62de0 --- /dev/null +++ b/src/app/core/bitstream-checksum-data.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { dataService } from './data/base/data-service.decorator'; +import { BaseDataService } from './data/base/base-data.service'; +import { RequestService } from './data/request.service'; +import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; +import { Store } from '@ngrx/store'; +import { CoreState } from './core-state.model'; +import { HALEndpointService } from './shared/hal-endpoint.service'; +import { ObjectCacheService } from './cache/object-cache.service'; +import { DefaultChangeAnalyzer } from './data/default-change-analyzer.service'; +import { HttpClient } from '@angular/common/http'; +import { NotificationsService } from '../shared/notifications/notifications.service'; +import { linkName } from './data/clarin/clrua-data.service'; +import { BitstreamChecksum } from './shared/bitstream-checksum.model'; + +/** + * A service responsible for fetching BitstreamChecksum objects from the REST API + */ +@Injectable() +@dataService(BitstreamChecksum.type) +export class BitstreamChecksumDataService extends BaseDataService { + protected linkPath = 'checksum'; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected halService: HALEndpointService, + protected objectCache: ObjectCacheService, + protected comparator: DefaultChangeAnalyzer, + protected http: HttpClient, + protected notificationsService: NotificationsService, + ) { + super(linkName, requestService, rdbService, objectCache, halService, undefined); + } +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 306b950f8f4..20a32beef40 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -189,6 +189,8 @@ import { ClarinUserMetadataDataService } from './data/clarin/clarin-user-metadat import { ClarinLicenseResourceMappingService } from './data/clarin/clarin-license-resource-mapping-data.service'; import { ClarinVerificationTokenDataService } from './data/clarin/clarin-verification-token-data.service'; import { ClruaDataService } from './data/clarin/clrua-data.service'; +import { BitstreamChecksum } from './shared/bitstream-checksum.model'; +import { BitstreamChecksumDataService } from './bitstream-checksum-data.service'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -322,7 +324,8 @@ const PROVIDERS = [ OrcidQueueDataService, OrcidHistoryDataService, SupervisionOrderDataService, - HandleDataService + HandleDataService, + BitstreamChecksumDataService ]; /** @@ -335,6 +338,7 @@ export const models = Bundle, Bitstream, BitstreamFormat, + BitstreamChecksum, Item, Site, Collection, diff --git a/src/app/core/shared/bitstream-checksum.model.ts b/src/app/core/shared/bitstream-checksum.model.ts new file mode 100644 index 00000000000..c6c282d0428 --- /dev/null +++ b/src/app/core/shared/bitstream-checksum.model.ts @@ -0,0 +1,63 @@ +import { BITSTREAM_CHECKSUM } from './bitstream-checksum.resource'; +import { excludeFromEquals } from '../utilities/equals.decorators'; +import { autoserialize, deserialize } from 'cerialize'; +import { ResourceType } from './resource-type'; +import { HALLink } from './hal-link.model'; +import { typedObject } from '../cache/builders/build-decorators'; +import { TypedObject } from '../cache/typed-object.model'; + + +/** + * Model class containing the checksums of a bitstream (local, S3, DB) + */ +@typedObject +export class BitstreamChecksum extends TypedObject { + /** + * The `bitstreamchecksum` object type. + */ + static type = BITSTREAM_CHECKSUM; + + /** + * The object type + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The identifier of this BitstreamChecksum object + */ + @autoserialize + id: string; + + /** + * The checksum of the active store (local/S3) + */ + @autoserialize + activeStore: CheckSum; + + /** + * The checksum from the database + */ + @autoserialize + databaseChecksum: CheckSum; + + /** + * The checksum of the synchronized store (S3, local) + */ + @autoserialize + synchronizedStore: CheckSum; + + @deserialize + _links: { + self: HALLink + }; +} + +/** + * Model class containing a checksum value and algorithm + */ +export interface CheckSum { + checkSumAlgorithm: string; + value: string; +} diff --git a/src/app/core/shared/bitstream-checksum.resource.ts b/src/app/core/shared/bitstream-checksum.resource.ts new file mode 100644 index 00000000000..8404c0e7ca6 --- /dev/null +++ b/src/app/core/shared/bitstream-checksum.resource.ts @@ -0,0 +1,9 @@ +import { ResourceType } from './resource-type'; + +/** + * The resource type for BitstreamChecksum + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const BITSTREAM_CHECKSUM = new ResourceType('bitstreamchecksum'); diff --git a/src/app/core/shared/bitstream.model.ts b/src/app/core/shared/bitstream.model.ts index bbc5455eead..fe5aed6dab0 100644 --- a/src/app/core/shared/bitstream.model.ts +++ b/src/app/core/shared/bitstream.model.ts @@ -10,6 +10,8 @@ import { HALLink } from './hal-link.model'; import {BUNDLE} from './bundle.resource-type'; import {Bundle} from './bundle.model'; import { ChildHALResource } from './child-hal-resource.model'; +import { BITSTREAM_CHECKSUM } from './bitstream-checksum.resource'; +import { BitstreamChecksum } from './bitstream-checksum.model'; // Store number if the bitstream is stored in the both stores (S3 and local) export const SYNCHRONIZED_STORES_NUMBER = 77; @@ -53,6 +55,7 @@ export class Bitstream extends DSpaceObject implements ChildHALResource { format: HALLink; content: HALLink; thumbnail: HALLink; + checksum: HALLink; }; /** @@ -76,6 +79,12 @@ export class Bitstream extends DSpaceObject implements ChildHALResource { @link(BUNDLE) bundle?: Observable>; + /** + * The checksum values fetched from the DB, local and S3 store. + */ + @link(BITSTREAM_CHECKSUM) + checksum?: Observable>; + getParentLinkKey(): keyof this['_links'] { return 'format'; } diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts index 2c81a4e2cb9..a181bd3d944 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts @@ -60,7 +60,8 @@ export class PaginatedDragAndDropBitstreamListComponent extends AbstractPaginate switchMap(() => this.bundleService.getBitstreams( this.bundle.id, paginatedOptions, - followLink('format') + followLink('format'), + followLink('checksum') )) ); }) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html index adb15f2619a..dfd1c49fadf 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html @@ -51,8 +51,61 @@
- + + | +
+
+
+ + + +
+ + +
+
+ + +
+
+
+ {{'item.edit.bitstreams.checksum.database' | translate}} +
+
+ {{'item.edit.bitstreams.checksum.algorithm' | translate}} {{bitstreamChecksum.databaseChecksum.checkSumAlgorithm}} +
+
+ {{'item.edit.bitstreams.checksum.value' | translate}} {{ bitstreamChecksum.databaseChecksum.value }} +
+
+
+
+ {{'item.edit.bitstreams.checksum.active-store' | translate}} +
+
+ {{'item.edit.bitstreams.checksum.algorithm' | translate}} {{bitstreamChecksum.activeStore.checkSumAlgorithm}} +
+
+ {{'item.edit.bitstreams.checksum.value' | translate}} {{ bitstreamChecksum.activeStore.value }} +
+
+
+
+ {{'item.edit.bitstreams.checksum.sync-store' | translate}} +
+
+ {{'item.edit.bitstreams.checksum.algorithm' | translate}} {{bitstreamChecksum.synchronizedStore.checkSumAlgorithm}} +
+
+ {{'item.edit.bitstreams.checksum.value' | translate}} {{ bitstreamChecksum.synchronizedStore.value }} +
diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts index ca9aff92e1a..fec8a62448a 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts @@ -14,6 +14,7 @@ import { getBitstreamDownloadRoute } from '../../../../app-routing-paths'; import { By } from '@angular/platform-browser'; import { BrowserOnlyMockPipe } from '../../../../shared/testing/browser-only-mock.pipe'; import { RouterLinkDirectiveStub } from '../../../../shared/testing/router-link-directive.stub'; +import { BitstreamChecksum } from '../../../../core/shared/bitstream-checksum.model'; let comp: ItemEditBitstreamComponent; let fixture: ComponentFixture; @@ -29,6 +30,22 @@ const columnSizes = new ResponsiveTableSizes([ const format = Object.assign(new BitstreamFormat(), { shortDescription: 'PDF' }); + +const checksum = Object.assign(new BitstreamChecksum(), { + activeStore: { + checkSumAlgorithm: 'MD5', + value: '123' + }, + synchronizedStore: { + checkSumAlgorithm: 'MD5', + value: '456' + }, + databaseChecksum: { + checkSumAlgorithm: 'MD5', + value: '789' + } +}); + const bitstream = Object.assign(new Bitstream(), { uuid: 'bitstreamUUID', name: 'Fake Bitstream', @@ -38,7 +55,8 @@ const bitstream = Object.assign(new Bitstream(), { content: { href: 'content-link' } }, - format: createSuccessfulRemoteDataObject$(format) + format: createSuccessfulRemoteDataObject$(format), + checksum: createSuccessfulRemoteDataObject$(checksum) }); const fieldUpdate = { field: bitstream, @@ -82,7 +100,7 @@ describe('ItemEditBitstreamComponent', () => { RouterLinkDirectiveStub ], providers: [ - { provide: ObjectUpdatesService, useValue: objectUpdatesService } + { provide: ObjectUpdatesService, useValue: objectUpdatesService }, ], schemas: [ NO_ERRORS_SCHEMA ] diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts index ec527f349c9..a4c81901304 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts @@ -4,12 +4,17 @@ import cloneDeep from 'lodash/cloneDeep'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { Observable } from 'rxjs'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; -import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../../core/shared/operators'; +import { + getRemoteDataPayload, + getFirstSucceededRemoteData, + getFirstCompletedRemoteData +} from '../../../../core/shared/operators'; import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; import { getBitstreamDownloadRoute } from '../../../../app-routing-paths'; +import { BitstreamChecksum, CheckSum } from '../../../../core/shared/bitstream-checksum.model'; @Component({ selector: 'ds-item-edit-bitstream', @@ -64,9 +69,14 @@ export class ItemEditBitstreamComponent implements OnChanges, OnInit { format$: Observable; /** - * The value of the store number if the bitstream is stored in both stores (S3 and local) + * True on mouseover, false otherwise */ - syncStoresNumber = SYNCHRONIZED_STORES_NUMBER; + showChecksumValues = false; + + /** + * Object containing all checksums + */ + checkSum$: Observable; constructor(private objectUpdatesService: ObjectUpdatesService, private dsoNameService: DSONameService, @@ -89,6 +99,10 @@ export class ItemEditBitstreamComponent implements OnChanges, OnInit { getFirstSucceededRemoteData(), getRemoteDataPayload() ); + this.checkSum$ = this.bitstream.checksum.pipe( + getFirstCompletedRemoteData(), + getRemoteDataPayload() + ); } /** @@ -119,4 +133,37 @@ export class ItemEditBitstreamComponent implements OnChanges, OnInit { return this.fieldUpdate.changeType >= 0; } + /** + * Compare if two checksums are equal + * + * @param checksum1 e.g. DB checksum + * @param checksum2 e.g. Active store checksum (local or S3) + */ + compareChecksums(checksum1: CheckSum, checksum2: CheckSum): boolean { + return checksum1.value === checksum2.value && checksum1.checkSumAlgorithm === checksum2.checkSumAlgorithm; + } + + /** + * Compare if all checksums are equal (DB, Active store, Synchronized store) + * + * @param bitstreamChecksum which contains all checksums + */ + checksumsAreEqual(bitstreamChecksum: BitstreamChecksum): boolean { + if (this.isBitstreamSynchronized()) { + // Compare DB and Active store checksums + // Compare DB and Synchronized and Active store checksums + return this.compareChecksums(bitstreamChecksum.databaseChecksum, bitstreamChecksum.activeStore) && + this.compareChecksums(bitstreamChecksum.synchronizedStore, bitstreamChecksum.activeStore); + } + // Compare DB and Active store checksums + return this.compareChecksums(bitstreamChecksum.databaseChecksum, bitstreamChecksum.activeStore); + } + + /** + * Check if the bitstream is stored in both stores (S3 and local) + */ + isBitstreamSynchronized() { + return this.bitstream?.storeNumber === SYNCHRONIZED_STORES_NUMBER; + } + } diff --git a/src/app/thumbnail/thumbnail.component.spec.ts b/src/app/thumbnail/thumbnail.component.spec.ts index 29aebe03fc2..1f9fa6caed4 100644 --- a/src/app/thumbnail/thumbnail.component.spec.ts +++ b/src/app/thumbnail/thumbnail.component.spec.ts @@ -290,7 +290,8 @@ describe('ThumbnailComponent', () => { bundle: { href: 'bundle.url' }, format: { href: 'format.url' }, content: { href: CONTENT }, - thumbnail: undefined + thumbnail: undefined, + checksum: undefined }; }); diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 784183489ec..f4d6d37b02c 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -3270,6 +3270,22 @@ "item.edit.bitstreams.upload-button" : "Nahrát", +// "item.edit.bitstreams.checksum.algorithm": "Algorithm: ", + "item.edit.bitstreams.checksum.algorithm": "Algoritmus: ", + +// "item.edit.bitstreams.checksum.value": "Value: ", + "item.edit.bitstreams.checksum.value": "Hodnota: ", + +// "item.edit.bitstreams.checksum.database": "DB", + "item.edit.bitstreams.checksum.database": "DB", + +// "item.edit.bitstreams.checksum.active-store": "Active store", + "item.edit.bitstreams.checksum.active-store": "Aktivní úložiště", + +// "item.edit.bitstreams.checksum.sync-store": "Sync store", + "item.edit.bitstreams.checksum.sync-store": "Sync úložiště", + + // "item.edit.delete.cancel": "Cancel", "item.edit.delete.cancel" : "Zrušit", diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 4721c5e73f6..1941e6efc9c 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2178,7 +2178,7 @@ "item.edit.bitstreams.empty": "This item doesn't contain any bitstreams. Click the upload button to create one.", - "item.edit.bitstreams.headers.synchronized": "Synchronized", + "item.edit.bitstreams.headers.synchronized": "Sync | Checksum", "item.edit.bitstreams.headers.actions": "Actions", @@ -2216,6 +2216,16 @@ "item.edit.bitstreams.upload-button": "Upload", + "item.edit.bitstreams.checksum.algorithm": "Algorithm: ", + + "item.edit.bitstreams.checksum.value": "Value: ", + + "item.edit.bitstreams.checksum.database": "DB", + + "item.edit.bitstreams.checksum.active-store": "Active store", + + "item.edit.bitstreams.checksum.sync-store": "Sync store", + "item.edit.delete.cancel": "Cancel",