Kaynağa Gözat

WIP: Refactor a storage module and integrate it with UI

Alex Siman 6 yıl önce
ebeveyn
işleme
74c8e0acb5

+ 31 - 38
src/lib.rs

@@ -18,7 +18,7 @@ pub mod governance;
 use governance::{council, election, proposals};
 pub mod storage;
 use storage::{
-    content_directory, data_directory, data_object_storage_registry, data_object_type_registry,
+    data_directory, data_object_storage_registry, data_object_type_registry,
     downloads,
 };
 mod membership;
@@ -60,8 +60,8 @@ pub use timestamp::Call as TimestampCall;
 /// Alias to Ed25519 pubkey that identifies an account on the chain.
 pub type AccountId = primitives::H256;
 
-/// Alias for ContentId, used in various places
-pub type ContentId = u64;
+/// Alias for ContentId, used in various places.
+pub type ContentId = primitives::H256;
 
 /// A hash of some data used by the chain.
 pub type Hash = primitives::H256;
@@ -251,6 +251,7 @@ impl storage::data_object_type_registry::Trait for Runtime {
 impl storage::data_directory::Trait for Runtime {
     type Event = Event;
     type ContentId = ContentId;
+    type SchemaId = u64;
     type Members = Members;
     type Roles = Actors;
     type IsActiveDataObjectType = DataObjectTypeRegistry;
@@ -270,13 +271,6 @@ impl storage::data_object_storage_registry::Trait for Runtime {
     type ContentIdExists = DataDirectory;
 }
 
-impl storage::content_directory::Trait for Runtime {
-    type Event = Event;
-    type MetadataId = u64;
-    type SchemaId = u64;
-    type Members = Members;
-}
-
 impl members::Trait for Runtime {
     type Event = Event;
     type MemberId = u64;
@@ -295,34 +289,33 @@ impl actors::Trait for Runtime {
 }
 
 construct_runtime!(
-	pub enum Runtime with Log(InternalLog: DigestItem<Hash, Ed25519AuthorityId>) where
-		Block = Block,
-		NodeBlock = opaque::Block,
-		UncheckedExtrinsic = UncheckedExtrinsic
-	{
-		System: system::{default, Log(ChangesTrieRoot)},
-		Timestamp: timestamp::{Module, Call, Storage, Config<T>, Inherent},
-		Consensus: consensus::{Module, Call, Storage, Config<T>, Log(AuthoritiesChange), Inherent},
-		Aura: aura::{Module, Inherent(Timestamp)},
-		Indices: indices,
-		Balances: balances,
-		Session: session,
-		Staking: staking::{default, OfflineWorker},
-		Fees: fees::{Module, Storage, Config<T>, Event<T>},
-		Sudo: sudo,
-		Proposals: proposals::{Module, Call, Storage, Event<T>, Config<T>},
-		CouncilElection: election::{Module, Call, Storage, Event<T>, Config<T>},
-		Council: council::{Module, Call, Storage, Event<T>, Config<T>},
-		Memo: memo::{Module, Call, Storage, Event<T>},
-		Members: members::{Module, Call, Storage, Event<T>, Config<T>},
-		Migration: migration::{Module, Call, Storage, Event<T>},
-		Actors: actors::{Module, Call, Storage, Event<T>},
-		DataObjectTypeRegistry: data_object_type_registry::{Module, Call, Storage, Event<T>, Config<T>},
-		DataDirectory: data_directory::{Module, Call, Storage, Event<T>},
-		DataObjectStorageRegistry: data_object_storage_registry::{Module, Call, Storage, Event<T>, Config<T>},
-		DownloadSessions: downloads::{Module, Call, Storage, Event<T>, Config<T>},
-		ContentDirectory: content_directory::{Module, Call, Storage, Event<T>, Config<T>},
-	}
+    pub enum Runtime with Log(InternalLog: DigestItem<Hash, Ed25519AuthorityId>) where
+        Block = Block,
+        NodeBlock = opaque::Block,
+        UncheckedExtrinsic = UncheckedExtrinsic
+    {
+        System: system::{default, Log(ChangesTrieRoot)},
+        Timestamp: timestamp::{Module, Call, Storage, Config<T>, Inherent},
+        Consensus: consensus::{Module, Call, Storage, Config<T>, Log(AuthoritiesChange), Inherent},
+        Aura: aura::{Module, Inherent(Timestamp)},
+        Indices: indices,
+        Balances: balances,
+        Session: session,
+        Staking: staking::{default, OfflineWorker},
+        Fees: fees::{Module, Storage, Config<T>, Event<T>},
+        Sudo: sudo,
+        Proposals: proposals::{Module, Call, Storage, Event<T>, Config<T>},
+        CouncilElection: election::{Module, Call, Storage, Event<T>, Config<T>},
+        Council: council::{Module, Call, Storage, Event<T>, Config<T>},
+        Memo: memo::{Module, Call, Storage, Event<T>},
+        Members: members::{Module, Call, Storage, Event<T>, Config<T>},
+        Migration: migration::{Module, Call, Storage, Event<T>},
+        Actors: actors::{Module, Call, Storage, Event<T>},
+        DataObjectTypeRegistry: data_object_type_registry::{Module, Call, Storage, Event<T>, Config<T>},
+        DataDirectory: data_directory::{Module, Call, Storage, Event<T>},
+        DataObjectStorageRegistry: data_object_storage_registry::{Module, Call, Storage, Event<T>, Config<T>},
+        DownloadSessions: downloads::{Module, Call, Storage, Event<T>, Config<T>},
+    }
 );
 
 /// The type used as a helper for interpreting the sender of transactions.

+ 0 - 201
src/storage/content_directory.rs

@@ -1,201 +0,0 @@
-#![cfg_attr(not(feature = "std"), no_std)]
-
-use crate::storage::data_object_type_registry::Trait as DOTRTrait;
-use crate::traits::Members;
-use parity_codec::Codec;
-use parity_codec_derive::{Decode, Encode};
-use rstd::prelude::*;
-use runtime_primitives::traits::{As, MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic};
-use srml_support::{
-    decl_event, decl_module, decl_storage, ensure, Parameter, StorageMap, StorageValue,
-};
-use system::{self, ensure_signed};
-
-pub trait Trait: timestamp::Trait + system::Trait + DOTRTrait + MaybeDebug {
-    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
-
-    type MetadataId: Parameter
-        + Member
-        + SimpleArithmetic
-        + Codec
-        + Default
-        + Copy
-        + As<usize>
-        + As<u64>
-        + MaybeSerializeDebug
-        + PartialEq;
-
-    // Schema ID should be defined in a different Trait in future
-    type SchemaId: Parameter
-        + Member
-        + SimpleArithmetic
-        + Codec
-        + Default
-        + Copy
-        + As<usize>
-        + As<u64>
-        + MaybeSerializeDebug
-        + PartialEq;
-
-    type Members: Members<Self>;
-}
-
-static MSG_CREATOR_MUST_BE_MEMBER: &str = "Only active members may create content!";
-// TODO for future: static MSG_INVALID_SCHEMA_ID: &str = "The metadata schema is not known or invalid!";
-static MSG_METADATA_NOT_FOUND: &str = "Metadata with the given ID cannot be found!";
-static MSG_ONLY_OWNER_MAY_PUBLISH: &str = "Only metadata owners may publish their metadata!";
-
-const DEFAULT_FIRST_METADATA_ID: u64 = 1;
-
-#[derive(Clone, Encode, Decode, PartialEq)]
-#[cfg_attr(feature = "std", derive(Debug))]
-pub enum MetadataState {
-    Draft,
-    Published,
-}
-
-impl Default for MetadataState {
-    fn default() -> Self {
-        MetadataState::Draft
-    }
-}
-
-// Content metadata contains two fields: one is the numeric ID of the metadata
-// scheme to use, which is currently related to off-chain definitions ONLY.
-// The other is the serialized metadata.
-#[derive(Clone, Encode, Decode, PartialEq)]
-#[cfg_attr(feature = "std", derive(Debug))]
-pub struct ContentMetadata<T: Trait> {
-    pub schema: T::SchemaId,
-    pub metadata: Vec<u8>,
-    pub origin: T::AccountId,
-    pub state: MetadataState,
-}
-
-decl_event! {
-    pub enum Event<T> where
-        <T as Trait>::MetadataId
-    {
-        MetadataDraftCreated(MetadataId),
-        MetadataPublished(MetadataId),
-    }
-}
-
-decl_storage! {
-    trait Store for Module<T: Trait> as ContentDirectory {
-        // Start at this value
-        pub FirstMetadataId get(first_metadata_id) config(first_metadata_id): T::MetadataId = T::MetadataId::sa(DEFAULT_FIRST_METADATA_ID);
-
-        // Increment
-        pub NextMetadataId get(next_metadata_id) build(|config: &GenesisConfig<T>| config.first_metadata_id): T::MetadataId = T::MetadataId::sa(DEFAULT_FIRST_METADATA_ID);
-
-        // Mapping of Data object types
-        pub MetadataMap get(metadata): map T::MetadataId => Option<ContentMetadata<T>>;
-    }
-}
-
-decl_module! {
-    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
-        fn deposit_event<T>() = default;
-
-        pub fn add_metadata(origin, schema: T::SchemaId, metadata: Vec<u8>)
-        {
-            // Origin has to be a member
-            let who = ensure_signed(origin)?;
-            ensure!(T::Members::is_active_member(&who), MSG_CREATOR_MUST_BE_MEMBER);
-
-            // TODO in future, we want the schema IDs to correspond to a schema
-            // registry, and validate the data. For now, we just allow the
-            // following schema IDs:
-            // 1 - Video (schema TBD, i.e. handled in UI only)
-            // 2 - Podcast (schema TBD, i.e. handled in UI only)
-            // Pseudocode
-            // if schema not in (1, 2) {
-            //     return Err(MSG_INVALID_SCHEMA_ID);
-            // }
-            //
-            // See https://github.com/Joystream/substrate-runtime-joystream/issues/20
-
-            // New and data
-            let new_id = Self::next_metadata_id();
-            let data: ContentMetadata<T> = ContentMetadata {
-                origin: who.clone(),
-                schema: schema,
-                metadata: metadata.clone(),
-                state: MetadataState::Draft,
-            };
-
-            // Store
-            <MetadataMap<T>>::insert(new_id, data);
-            <NextMetadataId<T>>::mutate(|n| { *n += T::MetadataId::sa(1); });
-
-            // Publish event
-            Self::deposit_event(RawEvent::MetadataDraftCreated(new_id));
-        }
-
-        pub fn publish_metadata(origin, id: T::MetadataId)
-        {
-            // Ensure signed account
-            let who = ensure_signed(origin)?;
-
-            // Try t find metadata
-            let mut data = Self::metadata(id).ok_or(MSG_METADATA_NOT_FOUND)?;
-
-            // Ensure it's the metadata creator who publishes
-            ensure!(data.origin == who, MSG_ONLY_OWNER_MAY_PUBLISH);
-
-            // Modify
-            data.state = MetadataState::Published;
-
-            // Store
-            <MetadataMap<T>>::insert(id, data);
-
-            // Publish event
-            Self::deposit_event(RawEvent::MetadataPublished(id));
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::storage::mock::*;
-
-    #[test]
-    fn add_metadata() {
-        with_default_mock_builder(|| {
-            let res =
-                TestContentDirectory::add_metadata(Origin::signed(1), 1, "foo".as_bytes().to_vec());
-            assert!(res.is_ok());
-        });
-    }
-
-    #[test]
-    fn publish_metadata() {
-        with_default_mock_builder(|| {
-            let res =
-                TestContentDirectory::add_metadata(Origin::signed(1), 1, "foo".as_bytes().to_vec());
-            assert!(res.is_ok());
-
-            // Grab ID from event
-            let metadata_id = match System::events().last().unwrap().event {
-                MetaEvent::content_directory(
-                    content_directory::RawEvent::MetadataDraftCreated(metadata_id),
-                ) => metadata_id,
-                _ => 0xdeadbeefu64, // invalid value, unlikely to match
-            };
-            assert_ne!(metadata_id, 0xdeadbeefu64);
-
-            // Publishing a bad ID should fail
-            let res = TestContentDirectory::publish_metadata(Origin::signed(1), metadata_id + 1);
-            assert!(res.is_err());
-
-            // Publishing should not work for non-owners
-            let res = TestContentDirectory::publish_metadata(Origin::signed(2), metadata_id);
-            assert!(res.is_err());
-
-            // For the owner, it should work however
-            let res = TestContentDirectory::publish_metadata(Origin::signed(1), metadata_id);
-            assert!(res.is_ok());
-        });
-    }
-}

+ 241 - 70
src/storage/data_directory.rs

@@ -5,9 +5,8 @@ use crate::storage::data_object_type_registry::Trait as DOTRTrait;
 use crate::traits::{ContentIdExists, IsActiveDataObjectType, Members, Roles};
 use parity_codec::Codec;
 use parity_codec_derive::{Decode, Encode};
-use primitives::Ed25519AuthorityId;
 use rstd::prelude::*;
-use runtime_primitives::traits::{As, MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic};
+use runtime_primitives::traits::{As, MaybeDebug, MaybeSerializeDebug, Member, MaybeDisplay, SimpleArithmetic};
 use srml_support::{
     decl_event, decl_module, decl_storage, dispatch, ensure, Parameter, StorageMap, StorageValue,
 };
@@ -16,16 +15,10 @@ use system::{self, ensure_signed};
 pub trait Trait: timestamp::Trait + system::Trait + DOTRTrait + MaybeDebug {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    type ContentId: Parameter
-        + Member
-        + SimpleArithmetic
-        + Codec
-        + Default
-        + Copy
-        + As<usize>
-        + As<u64>
-        + MaybeSerializeDebug
-        + PartialEq;
+    type ContentId: Parameter + Member + MaybeSerializeDebug + MaybeDisplay + Copy + Ord + Default;
+
+    type SchemaId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy
+        + As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
 
     type Members: Members<Self>;
     type Roles: Roles<Self>;
@@ -38,14 +31,19 @@ static MSG_CREATOR_MUST_BE_MEMBER: &str = "Only active members may create conten
 static MSG_DO_TYPE_MUST_BE_ACTIVE: &str =
     "Cannot create content for inactive or missing data object type.";
 
-const DEFAULT_FIRST_CONTENT_ID: u64 = 1;
+#[derive(Clone, Encode, Decode, PartialEq)]
+#[cfg_attr(feature = "std", derive(Debug))]
+pub struct BlockAndTime<T: Trait> {
+    pub block: T::BlockNumber,
+    pub time: T::Moment,
+}
 
 #[derive(Clone, Encode, Decode, PartialEq)]
 #[cfg_attr(feature = "std", derive(Debug))]
 pub enum LiaisonJudgement {
     Pending,
-    Rejected,
     Accepted,
+    Rejected,
 }
 
 impl Default for LiaisonJudgement {
@@ -57,26 +55,68 @@ impl Default for LiaisonJudgement {
 #[derive(Clone, Encode, Decode, PartialEq)]
 #[cfg_attr(feature = "std", derive(Debug))]
 pub struct DataObject<T: Trait> {
-    pub data_object_type: <T as DOTRTrait>::DataObjectTypeId,
-    pub signing_key: Option<Ed25519AuthorityId>,
-    pub size: u64,
-    pub added_at_block: T::BlockNumber,
-    pub added_at_time: T::Moment,
     pub owner: T::AccountId,
+    pub added_at: BlockAndTime<T>,
+    pub type_id: <T as DOTRTrait>::DataObjectTypeId,
+    pub size: u64,
     pub liaison: T::AccountId,
     pub liaison_judgement: LiaisonJudgement,
+
+    // TODO signing_key: public key supplied by the uploader, 
+    // they sigh the content with this key
+
+    // TODO add support for this field (Some if judgment == Rejected)
+    // pub rejection_reason: Option<Vec<u8>>,
+}
+
+#[derive(Clone, Encode, Decode, PartialEq)]
+#[cfg_attr(feature = "std", derive(Debug))]
+// TODO ContentVisibility
+pub enum ContentVisibility {
+    Draft, // TODO rename to Unlisted?
+    Public,
+}
+
+impl Default for ContentVisibility {
+    fn default() -> Self {
+        ContentVisibility::Draft // TODO make Public by default?
+    }
+}
+
+#[derive(Clone, Encode, Decode, PartialEq)]
+#[cfg_attr(feature = "std", derive(Debug))]
+pub struct ContentMetadata<T: Trait> {
+    pub owner: T::AccountId,
+    pub added_at: BlockAndTime<T>,
+    pub children_ids: Vec<T::ContentId>,
+    pub visibility: ContentVisibility,
+    pub schema: T::SchemaId,
+    pub json: Vec<u8>,
+}
+
+#[derive(Clone, Encode, Decode, PartialEq)]
+#[cfg_attr(feature = "std", derive(Debug))]
+pub struct ContentMetadataUpdate<T: Trait> {
+    pub children_ids: Option<Vec<T::ContentId>>,
+    pub visibility: Option<ContentVisibility>,
+    pub schema: Option<T::SchemaId>,
+    pub json: Option<Vec<u8>>,
 }
 
 decl_storage! {
     trait Store for Module<T: Trait> as DataDirectory {
-        // Start at this value
-        pub FirstContentId get(first_content_id) config(first_content_id): T::ContentId = T::ContentId::sa(DEFAULT_FIRST_CONTENT_ID);
 
-        // Increment
-        pub NextContentId get(next_content_id) build(|config: &GenesisConfig<T>| config.first_content_id): T::ContentId = T::ContentId::sa(DEFAULT_FIRST_CONTENT_ID);
+        // TODO default_liaison = Joystream storage account id.
+
+        // TODO this list of ids should be moved off-chain once we have Content Indexer.
+        // TODO deprecated, moved tp storage relationship
+        KnownContentIds get(known_content_ids): Vec<T::ContentId> = vec![];
 
-        // Mapping of Content ID to Data Object
-        pub Contents get(contents): map T::ContentId => Option<DataObject<T>>;
+        DataObjectByContentId get(data_object_by_content_id):
+            map T::ContentId => Option<DataObject<T>>;
+
+        MetadataByContentId get(metadata_by_content_id):
+            map T::ContentId => Option<ContentMetadata<T>>;
     }
 }
 
@@ -85,25 +125,16 @@ decl_event! {
         <T as Trait>::ContentId,
         <T as system::Trait>::AccountId
     {
-        // The account is the Liaison that was selected
+        // The account is the one who uploaded the content.
         ContentAdded(ContentId, AccountId),
 
-        // The account is the liaison again - only they can reject or accept
+        // The account is the liaison - only they can reject or accept
         ContentAccepted(ContentId, AccountId),
         ContentRejected(ContentId, AccountId),
-    }
-}
 
-impl<T: Trait> ContentIdExists<T> for Module<T> {
-    fn has_content(which: &T::ContentId) -> bool {
-        Self::contents(which.clone()).is_some()
-    }
-
-    fn get_data_object(which: &T::ContentId) -> Result<DataObject<T>, &'static str> {
-        match Self::contents(which.clone()) {
-            None => Err(MSG_CID_NOT_FOUND),
-            Some(data) => Ok(data),
-        }
+        // The account is the owner of the content.
+        MetadataAdded(ContentId, AccountId),
+        MetadataUpdated(ContentId, AccountId),
     }
 }
 
@@ -111,71 +142,170 @@ decl_module! {
     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
         fn deposit_event<T>() = default;
 
-        pub fn add_content(origin, data_object_type_id: <T as DOTRTrait>::DataObjectTypeId,
-                           size: u64, signing_key: Option<Ed25519AuthorityId>) {
-            // Origin has to be a member
+        // TODO send file_name as param so we could create a Draft metadata in this fn
+        pub fn add_content(
+            origin,
+            content_id: T::ContentId,
+            type_id: <T as DOTRTrait>::DataObjectTypeId,
+            size: u64
+        ) {
             let who = ensure_signed(origin)?;
             ensure!(T::Members::is_active_member(&who), MSG_CREATOR_MUST_BE_MEMBER);
 
-            // Data object type has to be active
-            ensure!(T::IsActiveDataObjectType::is_active_data_object_type(&data_object_type_id), MSG_DO_TYPE_MUST_BE_ACTIVE);
+            ensure!(T::IsActiveDataObjectType::is_active_data_object_type(&type_id), 
+                MSG_DO_TYPE_MUST_BE_ACTIVE);
+
+            ensure!(!<DataObjectByContentId<T>>::exists(content_id), 
+                "Data object aready added under this content id");
 
             // The liaison is something we need to take from staked roles. The idea
             // is to select the liaison, for now randomly.
             let liaison = T::Roles::random_account_for_role(actors::Role::Storage)?;
 
             // Let's create the entry then
-            let new_id = Self::next_content_id();
             let data: DataObject<T> = DataObject {
-                data_object_type: data_object_type_id,
-                signing_key: signing_key,
-                size: size,
-                added_at_block: <system::Module<T>>::block_number(),
-                added_at_time: <timestamp::Module<T>>::now(),
-                owner: who,
-                liaison: liaison.clone(),
+                type_id,
+                size,
+                added_at: Self::current_block_and_time(),
+                owner: who.clone(),
+                liaison: liaison,
                 liaison_judgement: LiaisonJudgement::Pending,
             };
 
-            // If we've constructed the data, we can store it and send an event.
-            <Contents<T>>::insert(new_id, data);
-            <NextContentId<T>>::mutate(|n| { *n += T::ContentId::sa(1); });
-
-            Self::deposit_event(RawEvent::ContentAdded(new_id, liaison));
+            <DataObjectByContentId<T>>::insert(&content_id, data);
+            Self::deposit_event(RawEvent::ContentAdded(content_id, who));
         }
 
         // The LiaisonJudgement can be updated, but only by the liaison.
-        fn accept_content(origin, id: T::ContentId) {
+        fn accept_content(origin, content_id: T::ContentId) {
+            let who = ensure_signed(origin)?;
+            Self::update_content_judgement(&who, content_id.clone(), LiaisonJudgement::Accepted)?;
+            Self::deposit_event(RawEvent::ContentAccepted(content_id, who));
+        }
+
+        fn reject_content(origin, content_id: T::ContentId) {
+            let who = ensure_signed(origin)?;
+            Self::update_content_judgement(&who, content_id.clone(), LiaisonJudgement::Rejected)?;
+            Self::deposit_event(RawEvent::ContentRejected(content_id, who));
+        }
+
+        fn add_metadata(
+            origin,
+            content_id: T::ContentId,
+            update: ContentMetadataUpdate<T>
+        ) {
             let who = ensure_signed(origin)?;
-            Self::update_content_judgement(&who, id.clone(), LiaisonJudgement::Accepted)?;
-            Self::deposit_event(RawEvent::ContentAccepted(id, who));
+            ensure!(T::Members::is_active_member(&who), 
+                "Only active members can add content metadata");
+
+            ensure!(!<MetadataByContentId<T>>::exists(&content_id), 
+                "Metadata aready added under this content id");
+
+            let schema = update.schema.ok_or("Schema is required")?;
+            Self::validate_metadata_schema(&schema)?;
+
+            let json = update.json.ok_or("JSON is required")?;
+            Self::validate_metadata_json(&json)?;
+
+            let meta = ContentMetadata {
+                owner: who.clone(),
+                added_at: Self::current_block_and_time(),
+                children_ids: vec![],
+                visibility: update.visibility.unwrap_or_default(),
+                schema,
+                json,
+            };
+
+            // TODO temporary hack!!!
+            // TODO create Storage Relationship. ready = true
+
+            <MetadataByContentId<T>>::insert(&content_id, meta);
+            <KnownContentIds<T>>::mutate(|ids| ids.push(content_id));
+            Self::deposit_event(RawEvent::MetadataAdded(content_id, who));
         }
 
-        fn reject_content(origin, id: T::ContentId) {
+        fn update_metadata(
+            origin,
+            content_id: T::ContentId,
+            update: ContentMetadataUpdate<T>
+        ) {
             let who = ensure_signed(origin)?;
-            Self::update_content_judgement(&who, id.clone(), LiaisonJudgement::Rejected)?;
-            Self::deposit_event(RawEvent::ContentRejected(id, who));
+
+            // Even if origin is an owner of metadata, they stil need to be an active member.
+            ensure!(T::Members::is_active_member(&who), 
+                "Only active members can update content metadata");
+
+            let has_updates = update.schema.is_some() || update.json.is_some();
+            ensure!(has_updates, "No updates provided");
+
+            let mut meta = Self::metadata_by_content_id(&content_id)
+                .ok_or("No metadata found by content id")?;
+
+            ensure!(meta.owner == who.clone(), "Only owner can update content metadata");
+
+            if let Some(schema) = update.schema {
+                Self::validate_metadata_schema(&schema)?;
+                meta.schema = schema;
+            }
+            if let Some(json) = update.json {
+                Self::validate_metadata_json(&json)?;
+                meta.json = json;
+            }
+            if let Some(visibility) = update.visibility {
+                meta.visibility = visibility;
+            }
+
+            <MetadataByContentId<T>>::insert(&content_id, meta);
+            Self::deposit_event(RawEvent::MetadataUpdated(content_id, who));
+        }
+    }
+}
+
+impl<T: Trait> ContentIdExists<T> for Module<T> {
+
+    fn has_content(content_id: &T::ContentId) -> bool {
+        Self::data_object_by_content_id(content_id.clone()).is_some()
+    }
+
+    fn get_data_object(content_id: &T::ContentId) -> Result<DataObject<T>, &'static str> {
+        match Self::data_object_by_content_id(content_id.clone()) {
+            Some(data) => Ok(data),
+            None => Err(MSG_CID_NOT_FOUND),
         }
     }
 }
 
 impl<T: Trait> Module<T> {
+
+    fn current_block_and_time() -> BlockAndTime<T> {
+        BlockAndTime {
+            block: <system::Module<T>>::block_number(),
+            time: <timestamp::Module<T>>::now(),
+        }
+    }
+
+    fn validate_metadata_schema(_schema: &T::SchemaId) -> dispatch::Result {
+        // TODO validate that schema id is registered.
+        Ok(())
+    }
+
+    fn validate_metadata_json(_json: &Vec<u8>) -> dispatch::Result {
+        // TODO validate a max length of JSON.
+        Ok(())
+    }
+
     fn update_content_judgement(
         who: &T::AccountId,
-        id: T::ContentId,
+        content_id: T::ContentId,
         judgement: LiaisonJudgement,
     ) -> dispatch::Result {
-        // Find the data
-        let mut data = Self::contents(&id).ok_or(MSG_CID_NOT_FOUND)?;
+        let mut data = Self::data_object_by_content_id(&content_id).ok_or(MSG_CID_NOT_FOUND)?;
 
         // Make sure the liaison matches
         ensure!(data.liaison == *who, MSG_LIAISON_REQUIRED);
 
-        // At this point we can update the data.
         data.liaison_judgement = judgement;
-
-        // Update and send event.
-        <Contents<T>>::insert(id, data);
+        <DataObjectByContentId<T>>::insert(content_id, data);
 
         Ok(())
     }
@@ -247,4 +377,45 @@ mod tests {
             assert!(res.is_ok());
         });
     }
+
+    // TODO update and add more tests for metadata
+
+    // #[test]
+    // fn add_metadata() {
+    //     with_default_mock_builder(|| {
+    //         let res =
+    //             TestContentDirectory::add_metadata(Origin::signed(1), 1, "foo".as_bytes().to_vec());
+    //         assert!(res.is_ok());
+    //     });
+    // }
+
+    // #[test]
+    // fn publish_metadata() {
+    //     with_default_mock_builder(|| {
+    //         let res =
+    //             TestContentDirectory::add_metadata(Origin::signed(1), 1, "foo".as_bytes().to_vec());
+    //         assert!(res.is_ok());
+
+    //         // Grab ID from event
+    //         let metadata_id = match System::events().last().unwrap().event {
+    //             MetaEvent::content_directory(
+    //                 content_directory::RawEvent::MetadataDraftCreated(metadata_id),
+    //             ) => metadata_id,
+    //             _ => 0xdeadbeefu64, // invalid value, unlikely to match
+    //         };
+    //         assert_ne!(metadata_id, 0xdeadbeefu64);
+
+    //         // Publishing a bad ID should fail
+    //         let res = TestContentDirectory::publish_metadata(Origin::signed(1), metadata_id + 1);
+    //         assert!(res.is_err());
+
+    //         // Publishing should not work for non-owners
+    //         let res = TestContentDirectory::publish_metadata(Origin::signed(2), metadata_id);
+    //         assert!(res.is_err());
+
+    //         // For the owner, it should work however
+    //         let res = TestContentDirectory::publish_metadata(Origin::signed(1), metadata_id);
+    //         assert!(res.is_ok());
+    //     });
+    // }
 }

+ 31 - 10
src/storage/data_object_storage_registry.rs

@@ -15,16 +15,9 @@ use system::{self, ensure_signed};
 pub trait Trait: timestamp::Trait + system::Trait + DDTrait + MaybeDebug {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    type DataObjectStorageRelationshipId: Parameter
-        + Member
-        + SimpleArithmetic
-        + Codec
-        + Default
-        + Copy
-        + As<usize>
-        + As<u64>
-        + MaybeSerializeDebug
-        + PartialEq;
+    // TODO deprecated
+    type DataObjectStorageRelationshipId: Parameter + Member + SimpleArithmetic + Codec 
+        + Default + Copy + As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
 
     type Members: Members<Self>;
     type Roles: Roles<Self>;
@@ -38,8 +31,10 @@ static MSG_ONLY_STORAGE_PROVIDER_MAY_CREATE_DOSR: &str =
 static MSG_ONLY_STORAGE_PROVIDER_MAY_CLAIM_READY: &str =
     "Only the storage provider in a DOSR can decide whether they're ready.";
 
+// TODO deprecated
 const DEFAULT_FIRST_RELATIONSHIP_ID: u64 = 1;
 
+// TODO deprecated
 #[derive(Clone, Encode, Decode, PartialEq)]
 #[cfg_attr(feature = "std", derive(Debug))]
 pub struct DataObjectStorageRelationship<T: Trait> {
@@ -50,17 +45,35 @@ pub struct DataObjectStorageRelationship<T: Trait> {
 
 decl_storage! {
     trait Store for Module<T: Trait> as DataObjectStorageRegistry {
+        
+        // TODO deprecated
         // Start at this value
         pub FirstRelationshipId get(first_relationship_id) config(first_relationship_id): T::DataObjectStorageRelationshipId = T::DataObjectStorageRelationshipId::sa(DEFAULT_FIRST_RELATIONSHIP_ID);
 
+        // TODO deprecated
         // Increment
         pub NextRelationshipId get(next_relationship_id) build(|config: &GenesisConfig<T>| config.first_relationship_id): T::DataObjectStorageRelationshipId = T::DataObjectStorageRelationshipId::sa(DEFAULT_FIRST_RELATIONSHIP_ID);
 
+        // TODO deprecated
         // Mapping of Data object types
         pub Relationships get(relationships): map T::DataObjectStorageRelationshipId => Option<DataObjectStorageRelationship<T>>;
 
+        // TODO deprecated
         // Keep a list of storage relationships per CID
         pub RelationshipsByContentId get(relationships_by_content_id): map T::ContentId => Vec<T::DataObjectStorageRelationshipId>;
+
+        // ------------------------------------------
+        // TODO use next storage items insteam:
+
+        // TODO save only if metadata exists and there is at least one relation w/ ready == true.
+        ReadyContentIds get(ready_content_ids): Vec<T::ContentId> = vec![];
+
+        // TODO need? it can be expressed via StorageProvidersByContentId
+        pub StorageProviderServesContent get(storage_provider_serves_content):
+            map (T::AccountId, T::ContentId) => bool;
+
+        pub StorageProvidersByContentId get(storage_providers_by_content_id):
+            map T::ContentId => Vec<T::AccountId>;
     }
 }
 
@@ -70,12 +83,19 @@ decl_event! {
         <T as Trait>::DataObjectStorageRelationshipId,
         <T as system::Trait>::AccountId
     {
+        // TODO deprecated
         DataObjectStorageRelationshipAdded(DataObjectStorageRelationshipId, ContentId, AccountId),
         DataObjectStorageRelationshipReadyUpdated(DataObjectStorageRelationshipId, bool),
+        
+        // NEW & COOL
+        StorageProviderAddedContent(AccountId, ContentId),
+        StorageProviderRemovedContent(AccountId, ContentId),
     }
 }
 
 impl<T: Trait> ContentHasStorage<T> for Module<T> {
+
+    // TODO deprecated
     fn has_storage_provider(which: &T::ContentId) -> bool {
         let dosr_list = Self::relationships_by_content_id(which);
         return dosr_list.iter().any(|&dosr_id| {
@@ -88,6 +108,7 @@ impl<T: Trait> ContentHasStorage<T> for Module<T> {
         });
     }
 
+    // TODO deprecated
     fn is_ready_at_storage_provider(which: &T::ContentId, provider: &T::AccountId) -> bool {
         let dosr_list = Self::relationships_by_content_id(which);
         return dosr_list.iter().any(|&dosr_id| {

+ 43 - 29
src/storage/data_object_type_registry.rs

@@ -6,25 +6,20 @@ use parity_codec_derive::{Decode, Encode};
 use rstd::prelude::*;
 use runtime_primitives::traits::{As, MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic};
 use srml_support::{decl_event, decl_module, decl_storage, Parameter, StorageMap, StorageValue};
-use system::{self, ensure_root};
 
 pub trait Trait: system::Trait + MaybeDebug {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    type DataObjectTypeId: Parameter
-        + Member
-        + SimpleArithmetic
-        + Codec
-        + Default
-        + Copy
-        + As<usize>
-        + As<u64>
-        + MaybeSerializeDebug
-        + PartialEq;
+    type DataObjectTypeId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy
+        + As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
 }
 
 static MSG_DO_TYPE_NOT_FOUND: &str = "Data Object Type with the given ID not found.";
 
+const DEFAULT_TYPE_DESCRIPTION: &str = "Default data object type for audio and video content.";
+const DEFAULT_TYPE_ACTIVE: bool = true;
+const CREATE_DETAULT_TYPE: bool = true;
+
 const DEFAULT_FIRST_DATA_OBJECT_TYPE_ID: u64 = 1;
 
 #[derive(Clone, Encode, Decode, PartialEq)]
@@ -38,8 +33,20 @@ pub struct DataObjectType {
     // - storage tranches (empty is ok)
 }
 
+impl Default for DataObjectType {
+    fn default() -> Self {
+        DataObjectType {
+            description: DEFAULT_TYPE_DESCRIPTION.as_bytes().to_vec(),
+            active: DEFAULT_TYPE_ACTIVE,
+        }
+    }
+}
+
 decl_storage! {
     trait Store for Module<T: Trait> as DataObjectTypeRegistry {
+
+        // TODO hardcode data object type for ID 1
+
         // Start at this value
         pub FirstDataObjectTypeId get(first_data_object_type_id) config(first_data_object_type_id): T::DataObjectTypeId = T::DataObjectTypeId::sa(DEFAULT_FIRST_DATA_OBJECT_TYPE_ID);
 
@@ -47,7 +54,7 @@ decl_storage! {
         pub NextDataObjectTypeId get(next_data_object_type_id) build(|config: &GenesisConfig<T>| config.first_data_object_type_id): T::DataObjectTypeId = T::DataObjectTypeId::sa(DEFAULT_FIRST_DATA_OBJECT_TYPE_ID);
 
         // Mapping of Data object types
-        pub DataObjectTypeMap get(data_object_type): map T::DataObjectTypeId => Option<DataObjectType>;
+        pub DataObjectTypes get(data_object_types): map T::DataObjectTypeId => Option<DataObjectType>;
     }
 }
 
@@ -72,29 +79,38 @@ decl_module! {
     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
         fn deposit_event<T>() = default;
 
-        pub fn register_data_object_type(origin, data_object_type: DataObjectType) {
-            ensure_root(origin)?;
+        fn on_initialise() {
+            // Create a default data object type if it was not created yet.
+            if CREATE_DETAULT_TYPE && !<DataObjectTypes<T>>::exists(Self::first_data_object_type_id()) {
+                let do_type: DataObjectType = DataObjectType::default();
+                let new_type_id = Self::next_data_object_type_id();
 
+                <DataObjectTypes<T>>::insert(new_type_id, do_type);
+                <NextDataObjectTypeId<T>>::mutate(|n| { *n += T::DataObjectTypeId::sa(1); });
+            }
+        }
+
+        pub fn register_data_object_type(data_object_type: DataObjectType) {
             let new_do_type_id = Self::next_data_object_type_id();
             let do_type: DataObjectType = DataObjectType {
                 description: data_object_type.description.clone(),
                 active: data_object_type.active,
             };
 
-            <DataObjectTypeMap<T>>::insert(new_do_type_id, do_type);
+            <DataObjectTypes<T>>::insert(new_do_type_id, do_type);
             <NextDataObjectTypeId<T>>::mutate(|n| { *n += T::DataObjectTypeId::sa(1); });
 
             Self::deposit_event(RawEvent::DataObjectTypeRegistered(new_do_type_id));
         }
 
-        pub fn update_data_object_type(origin, id: T::DataObjectTypeId, data_object_type: DataObjectType) {
-            ensure_root(origin)?;
+        // TODO use DataObjectTypeUpdate
+        pub fn update_data_object_type(id: T::DataObjectTypeId, data_object_type: DataObjectType) {
             let mut do_type = Self::ensure_data_object_type(id)?;
 
             do_type.description = data_object_type.description.clone();
             do_type.active = data_object_type.active;
 
-            <DataObjectTypeMap<T>>::insert(id, do_type);
+            <DataObjectTypes<T>>::insert(id, do_type);
 
             Self::deposit_event(RawEvent::DataObjectTypeUpdated(id));
         }
@@ -102,34 +118,32 @@ decl_module! {
         // Activate and deactivate functions as separate functions, because
         // toggling DO types is likely a more common operation than updating
         // other aspects.
-        pub fn activate_data_object_type(origin, id: T::DataObjectTypeId) {
-            ensure_root(origin)?;
+        // TODO deprecate or express via update_data_type
+        pub fn activate_data_object_type(id: T::DataObjectTypeId) {
             let mut do_type = Self::ensure_data_object_type(id)?;
 
             do_type.active = true;
 
-            <DataObjectTypeMap<T>>::insert(id, do_type);
+            <DataObjectTypes<T>>::insert(id, do_type);
 
             Self::deposit_event(RawEvent::DataObjectTypeUpdated(id));
         }
 
-        pub fn deactivate_data_object_type(origin, id: T::DataObjectTypeId) {
-            ensure_root(origin)?;
+        pub fn deactivate_data_object_type(id: T::DataObjectTypeId) {
             let mut do_type = Self::ensure_data_object_type(id)?;
 
             do_type.active = false;
 
-            <DataObjectTypeMap<T>>::insert(id, do_type);
+            <DataObjectTypes<T>>::insert(id, do_type);
 
             Self::deposit_event(RawEvent::DataObjectTypeUpdated(id));
         }
-
     }
 }
 
 impl<T: Trait> Module<T> {
     fn ensure_data_object_type(id: T::DataObjectTypeId) -> Result<DataObjectType, &'static str> {
-        return Self::data_object_type(&id).ok_or(MSG_DO_TYPE_NOT_FOUND);
+        return Self::data_object_types(&id).ok_or(MSG_DO_TYPE_NOT_FOUND);
     }
 }
 
@@ -250,7 +264,7 @@ mod tests {
             );
 
             // Retrieve, and ensure it's not active.
-            let data = TestDataObjectTypeRegistry::data_object_type(TEST_FIRST_DATA_OBJECT_TYPE_ID);
+            let data = TestDataObjectTypeRegistry::data_object_types(TEST_FIRST_DATA_OBJECT_TYPE_ID);
             assert!(data.is_some());
             assert!(!data.unwrap().active);
 
@@ -273,7 +287,7 @@ mod tests {
             );
 
             // Ensure that the item is actually activated.
-            let data = TestDataObjectTypeRegistry::data_object_type(TEST_FIRST_DATA_OBJECT_TYPE_ID);
+            let data = TestDataObjectTypeRegistry::data_object_types(TEST_FIRST_DATA_OBJECT_TYPE_ID);
             assert!(data.is_some());
             assert!(data.unwrap().active);
 
@@ -283,7 +297,7 @@ mod tests {
                 TEST_FIRST_DATA_OBJECT_TYPE_ID,
             );
             assert!(res.is_ok());
-            let data = TestDataObjectTypeRegistry::data_object_type(TEST_FIRST_DATA_OBJECT_TYPE_ID);
+            let data = TestDataObjectTypeRegistry::data_object_types(TEST_FIRST_DATA_OBJECT_TYPE_ID);
             assert!(data.is_some());
             assert!(!data.unwrap().active);
         });

+ 1 - 0
src/storage/downloads.rs

@@ -64,6 +64,7 @@ pub struct DownloadSession<T: Trait> {
     pub content_id: <T as DDTrait>::ContentId,
     pub consumer: T::AccountId,
     pub distributor: T::AccountId,
+    // TODO use BlockAndTime instead
     pub initiated_at_block: T::BlockNumber,
     pub initiated_at_time: T::Moment,
     pub state: DownloadState,

+ 1 - 19
src/storage/mock.rs

@@ -1,7 +1,7 @@
 #![cfg(test)]
 
 pub use super::{
-    content_directory, data_directory, data_object_storage_registry, data_object_type_registry,
+    data_directory, data_object_storage_registry, data_object_type_registry,
 };
 use crate::governance::GovernanceCurrency;
 use crate::roles::actors;
@@ -27,7 +27,6 @@ impl_outer_event! {
         data_object_type_registry<T>,
         data_directory<T>,
         data_object_storage_registry<T>,
-        content_directory<T>,
         actors<T>,
         balances<T>,
     }
@@ -153,13 +152,6 @@ impl data_object_storage_registry::Trait for Test {
     type ContentIdExists = MockContent;
 }
 
-impl content_directory::Trait for Test {
-    type Event = MetaEvent;
-    type MetadataId = u64;
-    type SchemaId = u64;
-    type Members = MockMembers;
-}
-
 impl actors::Trait for Test {
     type Event = MetaEvent;
     type Members = MockMembers;
@@ -267,15 +259,6 @@ impl ExtBuilder {
             .0,
         );
 
-        t.extend(
-            content_directory::GenesisConfig::<Test> {
-                first_metadata_id: self.first_metadata_id,
-            }
-            .build_storage()
-            .unwrap()
-            .0,
-        );
-
         t.into()
     }
 }
@@ -286,7 +269,6 @@ pub type TestDataObjectType = data_object_type_registry::DataObjectType;
 pub type TestDataDirectory = data_directory::Module<Test>;
 // pub type TestDataObject = data_directory::DataObject<Test>;
 pub type TestDataObjectStorageRegistry = data_object_storage_registry::Module<Test>;
-pub type TestContentDirectory = content_directory::Module<Test>;
 pub type TestActors = actors::Module<Test>;
 
 pub fn with_default_mock_builder<R, F: FnOnce() -> R>(f: F) -> R {

+ 0 - 1
src/storage/mod.rs

@@ -1,6 +1,5 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
-pub mod content_directory;
 pub mod data_directory;
 pub mod data_object_storage_registry;
 pub mod data_object_type_registry;