diff --git a/media/css/seahub.css b/media/css/seahub.css
index 7f601ace54..0a1983d7e8 100644
--- a/media/css/seahub.css
+++ b/media/css/seahub.css
@@ -2632,6 +2632,7 @@ button.sf-dropdown-toggle:focus {
.view-link-alert p {
display: inline-block;
}
+.select-white,
.perm-add-perm,
.user-perm-add-perm,
.group-perm-add-perm,
diff --git a/media/js/sort_lib.js b/media/js/sort_lib.js
deleted file mode 100644
index d051791819..0000000000
--- a/media/js/sort_lib.js
+++ /dev/null
@@ -1,79 +0,0 @@
-function sort_lib_by_name() {
- //include 20902 chinese words, Unicode(19968-40869)
- var strChineseFirstPY = "YDYQSXMWZSSXJBYMGCCZQPSSQBYCDSCDQLDYLYBSSJGYZZJJFKCCLZDHWDWZJLJPFYYNWJJTMYHZWZHFLZPPQHGSCYYYNJQYXXGJHHSDSJNKKTMOMLCRXYPSNQSECCQZGGLLYJLMYZZSECYKYYHQWJSSGGYXYZYJWWKDJHYCHMYXJTLXJYQBYXZLDWRDJRWYSRLDZJPCBZJJBRCFTLECZSTZFXXZHTRQHYBDLYCZSSYMMRFMYQZPWWJJYFCRWFDFZQPYDDWYXKYJAWJFFXYPSFTZYHHYZYSWCJYXSCLCXXWZZXNBGNNXBXLZSZSBSGPYSYZDHMDZBQBZCWDZZYYTZHBTSYYBZGNTNXQYWQSKBPHHLXGYBFMJEBJHHGQTJCYSXSTKZHLYCKGLYSMZXYALMELDCCXGZYRJXSDLTYZCQKCNNJWHJTZZCQLJSTSTBNXBTYXCEQXGKWJYFLZQLYHYXSPSFXLMPBYSXXXYDJCZYLLLSJXFHJXPJBTFFYABYXBHZZBJYZLWLCZGGBTSSMDTJZXPTHYQTGLJSCQFZKJZJQNLZWLSLHDZBWJNCJZYZSQQYCQYRZCJJWYBRTWPYFTWEXCSKDZCTBZHYZZYYJXZCFFZZMJYXXSDZZOTTBZLQWFCKSZSXFYRLNYJMBDTHJXSQQCCSBXYYTSYFBXDZTGBCNSLCYZZPSAZYZZSCJCSHZQYDXLBPJLLMQXTYDZXSQJTZPXLCGLQTZWJBHCTSYJSFXYEJJTLBGXSXJMYJQQPFZASYJNTYDJXKJCDJSZCBARTDCLYJQMWNQNCLLLKBYBZZSYHQQLTWLCCXTXLLZNTYLNEWYZYXCZXXGRKRMTCNDNJTSYYSSDQDGHSDBJGHRWRQLYBGLXHLGTGXBQJDZPYJSJYJCTMRNYMGRZJCZGJMZMGXMPRYXKJNYMSGMZJYMKMFXMLDTGFBHCJHKYLPFMDXLQJJSMTQGZSJLQDLDGJYCALCMZCSDJLLNXDJFFFFJCZFMZFFPFKHKGDPSXKTACJDHHZDDCRRCFQYJKQCCWJDXHWJLYLLZGCFCQDSMLZPBJJPLSBCJGGDCKKDEZSQCCKJGCGKDJTJDLZYCXKLQSCGJCLTFPCQCZGWPJDQYZJJBYJHSJDZWGFSJGZKQCCZLLPSPKJGQJHZZLJPLGJGJJTHJJYJZCZMLZLYQBGJWMLJKXZDZNJQSYZMLJLLJKYWXMKJLHSKJGBMCLYYMKXJQLBMLLKMDXXKWYXYSLMLPSJQQJQXYXFJTJDXMXXLLCXQBSYJBGWYMBGGBCYXPJYGPEPFGDJGBHBNSQJYZJKJKHXQFGQZKFHYGKHDKLLSDJQXPQYKYBNQSXQNSZSWHBSXWHXWBZZXDMNSJBSBKBBZKLYLXGWXDRWYQZMYWSJQLCJXXJXKJEQXSCYETLZHLYYYSDZPAQYZCMTLSHTZCFYZYXYLJSDCJQAGYSLCQLYYYSHMRQQKLDXZSCSSSYDYCJYSFSJBFRSSZQSBXXPXJYSDRCKGJLGDKZJZBDKTCSYQPYHSTCLDJDHMXMCGXYZHJDDTMHLTXZXYLYMOHYJCLTYFBQQXPFBDFHHTKSQHZYYWCNXXCRWHOWGYJLEGWDQCWGFJYCSNTMYTOLBYGWQWESJPWNMLRYDZSZTXYQPZGCWXHNGPYXSHMYQJXZTDPPBFYHZHTJYFDZWKGKZBLDNTSXHQEEGZZYLZMMZYJZGXZXKHKSTXNXXWYLYAPSTHXDWHZYMPXAGKYDXBHNHXKDPJNMYHYLPMGOCSLNZHKXXLPZZLBMLSFBHHGYGYYGGBHSCYAQTYWLXTZQCEZYDQDQMMHTKLLSZHLSJZWFYHQSWSCWLQAZYNYTLSXTHAZNKZZSZZLAXXZWWCTGQQTDDYZTCCHYQZFLXPSLZYGPZSZNGLNDQTBDLXGTCTAJDKYWNSYZLJHHZZCWNYYZYWMHYCHHYXHJKZWSXHZYXLYSKQYSPSLYZWMYPPKBYGLKZHTYXAXQSYSHXASMCHKDSCRSWJPWXSGZJLWWSCHSJHSQNHCSEGNDAQTBAALZZMSSTDQJCJKTSCJAXPLGGXHHGXXZCXPDMMHLDGTYBYSJMXHMRCPXXJZCKZXSHMLQXXTTHXWZFKHCCZDYTCJYXQHLXDHYPJQXYLSYYDZOZJNYXQEZYSQYAYXWYPDGXDDXSPPYZNDLTWRHXYDXZZJHTCXMCZLHPYYYYMHZLLHNXMYLLLMDCPPXHMXDKYCYRDLTXJCHHZZXZLCCLYLNZSHZJZZLNNRLWHYQSNJHXYNTTTKYJPYCHHYEGKCTTWLGQRLGGTGTYGYHPYHYLQYQGCWYQKPYYYTTTTLHYHLLTYTTSPLKYZXGZWGPYDSSZZDQXSKCQNMJJZZBXYQMJRTFFBTKHZKBXLJJKDXJTLBWFZPPTKQTZTGPDGNTPJYFALQMKGXBDCLZFHZCLLLLADPMXDJHLCCLGYHDZFGYDDGCYYFGYDXKSSEBDHYKDKDKHNAXXYBPBYYHXZQGAFFQYJXDMLJCSQZLLPCHBSXGJYNDYBYQSPZWJLZKSDDTACTBXZDYZYPJZQSJNKKTKNJDJGYYPGTLFYQKASDNTCYHBLWDZHBBYDWJRYGKZYHEYYFJMSDTYFZJJHGCXPLXHLDWXXJKYTCYKSSSMTWCTTQZLPBSZDZWZXGZAGYKTYWXLHLSPBCLLOQMMZSSLCMBJCSZZKYDCZJGQQDSMCYTZQQLWZQZXSSFPTTFQMDDZDSHDTDWFHTDYZJYQJQKYPBDJYYXTLJHDRQXXXHAYDHRJLKLYTWHLLRLLRCXYLBWSRSZZSYMKZZHHKYHXKSMDSYDYCJPBZBSQLFCXXXNXKXWYWSDZYQOGGQMMYHCDZTTFJYYBGSTTTYBYKJDHKYXBELHTYPJQNFXFDYKZHQKZBYJTZBXHFDXKDASWTAWAJLDYJSFHBLDNNTNQJTJNCHXFJSRFWHZFMDRYJYJWZPDJKZYJYMPCYZNYNXFBYTFYFWYGDBNZZZDNYTXZEMMQBSQEHXFZMBMFLZZSRXYMJGSXWZJSPRYDJSJGXHJJGLJJYNZZJXHGXKYMLPYYYCXYTWQZSWHWLYRJLPXSLSXMFSWWKLCTNXNYNPSJSZHDZEPTXMYYWXYYSYWLXJQZQXZDCLEEELMCPJPCLWBXSQHFWWTFFJTNQJHJQDXHWLBYZNFJLALKYYJLDXHHYCSTYYWNRJYXYWTRMDRQHWQCMFJDYZMHMYYXJWMYZQZXTLMRSPWWCHAQBXYGZYPXYYRRCLMPYMGKSJSZYSRMYJSNXTPLNBAPPYPYLXYYZKYNLDZYJZCZNNLMZHHARQMPGWQTZMXXMLLHGDZXYHXKYXYCJMFFYYHJFSBSSQLXXNDYCANNMTCJCYPRRNYTYQNYYMBMSXNDLYLYSLJRLXYSXQMLLYZLZJJJKYZZCSFBZXXMSTBJGNXYZHLXNMCWSCYZYFZLXBRNNNYLBNRTGZQYSATSWRYHYJZMZDHZGZDWYBSSCSKXSYHYTXXGCQGXZZSHYXJSCRHMKKBXCZJYJYMKQHZJFNBHMQHYSNJNZYBKNQMCLGQHWLZNZSWXKHLJHYYBQLBFCDSXDLDSPFZPSKJYZWZXZDDXJSMMEGJSCSSMGCLXXKYYYLNYPWWWGYDKZJGGGZGGSYCKNJWNJPCXBJJTQTJWDSSPJXZXNZXUMELPXFSXTLLXCLJXJJLJZXCTPSWXLYDHLYQRWHSYCSQYYBYAYWJJJQFWQCQQCJQGXALDBZZYJGKGXPLTZYFXJLTPADKYQHPMATLCPDCKBMTXYBHKLENXDLEEGQDYMSAWHZMLJTWYGXLYQZLJEEYYBQQFFNLYXRDSCTGJGXYYNKLLYQKCCTLHJLQMKKZGCYYGLLLJDZGYDHZWXPYSJBZKDZGYZZHYWYFQYTYZSZYEZZLYMHJJHTSMQWYZLKYYWZCSRKQYTLTDXWCTYJKLWSQZWBDCQYNCJSRSZJLKCDCDTLZZZACQQZZDDXYPLXZBQJYLZLLLQDDZQJYJYJZYXNYYYNYJXKXDAZWYRDLJYYYRJLXLLDYXJCYWYWNQCCLDDNYYYNYCKCZHXXCCLGZQJGKWPPCQQJYSBZZXYJSQPXJPZBSBDSFNSFPZXHDWZTDWPPTFLZZBZDMYYPQJRSDZSQZSQXBDGCPZSWDWCSQZGMDHZXMWWFYBPDGPHTMJTHZSMMBGZMBZJCFZWFZBBZMQCFMBDMCJXLGPNJBBXGYHYYJGPTZGZMQBQTCGYXJXLWZKYDPDYMGCFTPFXYZTZXDZXTGKMTYBBCLBJASKYTSSQYYMSZXFJEWLXLLSZBQJJJAKLYLXLYCCTSXMCWFKKKBSXLLLLJYXTYLTJYYTDPJHNHNNKBYQNFQYYZBYYESSESSGDYHFHWTCJBSDZZTFDMXHCNJZYMQWSRYJDZJQPDQBBSTJGGFBKJBXTGQHNGWJXJGDLLTHZHHYYYYYYSXWTYYYCCBDBPYPZYCCZYJPZYWCBDLFWZCWJDXXHYHLHWZZXJTCZLCDPXUJCZZZLYXJJTXPHFXWPYWXZPTDZZBDZCYHJHMLXBQXSBYLRDTGJRRCTTTHYTCZWMXFYTWWZCWJWXJYWCSKYBZSCCTZQNHXNWXXKHKFHTSWOCCJYBCMPZZYKBNNZPBZHHZDLSYDDYTYFJPXYNGFXBYQXCBHXCPSXTYZDMKYSNXSXLHKMZXLYHDHKWHXXSSKQYHHCJYXGLHZXCSNHEKDTGZXQYPKDHEXTYKCNYMYYYPKQYYYKXZLTHJQTBYQHXBMYHSQCKWWYLLHCYYLNNEQXQWMCFBDCCMLJGGXDQKTLXKGNQCDGZJWYJJLYHHQTTTNWCHMXCXWHWSZJYDJCCDBQCDGDNYXZTHCQRXCBHZTQCBXWGQWYYBXHMBYMYQTYEXMQKYAQYRGYZSLFYKKQHYSSQYSHJGJCNXKZYCXSBXYXHYYLSTYCXQTHYSMGSCPMMGCCCCCMTZTASMGQZJHKLOSQYLSWTMXSYQKDZLJQQYPLSYCZTCQQPBBQJZCLPKHQZYYXXDTDDTSJCXFFLLCHQXMJLWCJCXTSPYCXNDTJSHJWXDQQJSKXYAMYLSJHMLALYKXCYYDMNMDQMXMCZNNCYBZKKYFLMCHCMLHXRCJJHSYLNMTJZGZGYWJXSRXCWJGJQHQZDQJDCJJZKJKGDZQGJJYJYLXZXXCDQHHHEYTMHLFSBDJSYYSHFYSTCZQLPBDRFRZTZYKYWHSZYQKWDQZRKMSYNBCRXQBJYFAZPZZEDZCJYWBCJWHYJBQSZYWRYSZPTDKZPFPBNZTKLQYHBBZPNPPTYZZYBQNYDCPJMMCYCQMCYFZZDCMNLFPBPLNGQJTBTTNJZPZBBZNJKLJQYLNBZQHKSJZNGGQSZZKYXSHPZSNBCGZKDDZQANZHJKDRTLZLSWJLJZLYWTJNDJZJHXYAYNCBGTZCSSQMNJPJYTYSWXZFKWJQTKHTZPLBHSNJZSYZBWZZZZLSYLSBJHDWWQPSLMMFBJDWAQYZTCJTBNNWZXQXCDSLQGDSDPDZHJTQQPSWLYYJZLGYXYZLCTCBJTKTYCZJTQKBSJLGMGZDMCSGPYNJZYQYYKNXRPWSZXMTNCSZZYXYBYHYZAXYWQCJTLLCKJJTJHGDXDXYQYZZBYWDLWQCGLZGJGQRQZCZSSBCRPCSKYDZNXJSQGXSSJMYDNSTZTPBDLTKZWXQWQTZEXNQCZGWEZKSSBYBRTSSSLCCGBPSZQSZLCCGLLLZXHZQTHCZMQGYZQZNMCOCSZJMMZSQPJYGQLJYJPPLDXRGZYXCCSXHSHGTZNLZWZKJCXTCFCJXLBMQBCZZWPQDNHXLJCTHYZLGYLNLSZZPCXDSCQQHJQKSXZPBAJYEMSMJTZDXLCJYRYYNWJBNGZZTMJXLTBSLYRZPYLSSCNXPHLLHYLLQQZQLXYMRSYCXZLMMCZLTZSDWTJJLLNZGGQXPFSKYGYGHBFZPDKMWGHCXMSGDXJMCJZDYCABXJDLNBCDQYGSKYDQTXDJJYXMSZQAZDZFSLQXYJSJZYLBTXXWXQQZBJZUFBBLYLWDSLJHXJYZJWTDJCZFQZQZZDZSXZZQLZCDZFJHYSPYMPQZMLPPLFFXJJNZZYLSJEYQZFPFZKSYWJJJHRDJZZXTXXGLGHYDXCSKYSWMMZCWYBAZBJKSHFHJCXMHFQHYXXYZFTSJYZFXYXPZLCHMZMBXHZZSXYFYMNCWDABAZLXKTCSHHXKXJJZJSTHYGXSXYYHHHJWXKZXSSBZZWHHHCWTZZZPJXSNXQQJGZYZYWLLCWXZFXXYXYHXMKYYSWSQMNLNAYCYSPMJKHWCQHYLAJJMZXHMMCNZHBHXCLXTJPLTXYJHDYYLTTXFSZHYXXSJBJYAYRSMXYPLCKDUYHLXRLNLLSTYZYYQYGYHHSCCSMZCTZQXKYQFPYYRPFFLKQUNTSZLLZMWWTCQQYZWTLLMLMPWMBZSSTZRBPDDTLQJJBXZCSRZQQYGWCSXFWZLXCCRSZDZMCYGGDZQSGTJSWLJMYMMZYHFBJDGYXCCPSHXNZCSBSJYJGJMPPWAFFYFNXHYZXZYLREMZGZCYZSSZDLLJCSQFNXZKPTXZGXJJGFMYYYSNBTYLBNLHPFZDCYFBMGQRRSSSZXYSGTZRNYDZZCDGPJAFJFZKNZBLCZSZPSGCYCJSZLMLRSZBZZLDLSLLYSXSQZQLYXZLSKKBRXBRBZCYCXZZZEEYFGKLZLYYHGZSGZLFJHGTGWKRAAJYZKZQTSSHJJXDCYZUYJLZYRZDQQHGJZXSSZBYKJPBFRTJXLLFQWJHYLQTYMBLPZDXTZYGBDHZZRBGXHWNJTJXLKSCFSMWLSDQYSJTXKZSCFWJLBXFTZLLJZLLQBLSQMQQCGCZFPBPHZCZJLPYYGGDTGWDCFCZQYYYQYSSCLXZSKLZZZGFFCQNWGLHQYZJJCZLQZZYJPJZZBPDCCMHJGXDQDGDLZQMFGPSYTSDYFWWDJZJYSXYYCZCYHZWPBYKXRYLYBHKJKSFXTZJMMCKHLLTNYYMSYXYZPYJQYCSYCWMTJJKQYRHLLQXPSGTLYYCLJSCPXJYZFNMLRGJJTYZBXYZMSJYJHHFZQMSYXRSZCWTLRTQZSSTKXGQKGSPTGCZNJSJCQCXHMXGGZTQYDJKZDLBZSXJLHYQGGGTHQSZPYHJHHGYYGKGGCWJZZYLCZLXQSFTGZSLLLMLJSKCTBLLZZSZMMNYTPZSXQHJCJYQXYZXZQZCPSHKZZYSXCDFGMWQRLLQXRFZTLYSTCTMJCXJJXHJNXTNRZTZFQYHQGLLGCXSZSJDJLJCYDSJTLNYXHSZXCGJZYQPYLFHDJSBPCCZHJJJQZJQDYBSSLLCMYTTMQTBHJQNNYGKYRQYQMZGCJKPDCGMYZHQLLSLLCLMHOLZGDYYFZSLJCQZLYLZQJESHNYLLJXGJXLYSYYYXNBZLJSSZCQQCJYLLZLTJYLLZLLBNYLGQCHXYYXOXCXQKYJXXXYKLXSXXYQXCYKQXQCSGYXXYQXYGYTQOHXHXPYXXXULCYEYCHZZCBWQBBWJQZSCSZSSLZYLKDESJZWMYMCYTSDSXXSCJPQQSQYLYYZYCMDJDZYWCBTJSYDJKCYDDJLBDJJSODZYSYXQQYXDHHGQQYQHDYXWGMMMAJDYBBBPPBCMUUPLJZSMTXERXJMHQNUTPJDCBSSMSSSTKJTSSMMTRCPLZSZMLQDSDMJMQPNQDXCFYNBFSDQXYXHYAYKQYDDLQYYYSSZBYDSLNTFQTZQPZMCHDHCZCWFDXTMYQSPHQYYXSRGJCWTJTZZQMGWJJTJHTQJBBHWZPXXHYQFXXQYWYYHYSCDYDHHQMNMTMWCPBSZPPZZGLMZFOLLCFWHMMSJZTTDHZZYFFYTZZGZYSKYJXQYJZQBHMBZZLYGHGFMSHPZFZSNCLPBQSNJXZSLXXFPMTYJYGBXLLDLXPZJYZJYHHZCYWHJYLSJEXFSZZYWXKZJLUYDTMLYMQJPWXYHXSKTQJEZRPXXZHHMHWQPWQLYJJQJJZSZCPHJLCHHNXJLQWZJHBMZYXBDHHYPZLHLHLGFWLCHYYTLHJXCJMSCPXSTKPNHQXSRTYXXTESYJCTLSSLSTDLLLWWYHDHRJZSFGXTSYCZYNYHTDHWJSLHTZDQDJZXXQHGYLTZPHCSQFCLNJTCLZPFSTPDYNYLGMJLLYCQHYSSHCHYLHQYQTMZYPBYWRFQYKQSYSLZDQJMPXYYSSRHZJNYWTQDFZBWWTWWRXCWHGYHXMKMYYYQMSMZHNGCEPMLQQMTCWCTMMPXJPJJHFXYYZSXZHTYBMSTSYJTTQQQYYLHYNPYQZLCYZHZWSMYLKFJXLWGXYPJYTYSYXYMZCKTTWLKSMZSYLMPWLZWXWQZSSAQSYXYRHSSNTSRAPXCPWCMGDXHXZDZYFJHGZTTSBJHGYZSZYSMYCLLLXBTYXHBBZJKSSDMALXHYCFYGMQYPJYCQXJLLLJGSLZGQLYCJCCZOTYXMTMTTLLWTGPXYMZMKLPSZZZXHKQYSXCTYJZYHXSHYXZKXLZWPSQPYHJWPJPWXQQYLXSDHMRSLZZYZWTTCYXYSZZSHBSCCSTPLWSSCJCHNLCGCHSSPHYLHFHHXJSXYLLNYLSZDHZXYLSXLWZYKCLDYAXZCMDDYSPJTQJZLNWQPSSSWCTSTSZLBLNXSMNYYMJQBQHRZWTYYDCHQLXKPZWBGQYBKFCMZWPZLLYYLSZYDWHXPSBCMLJBSCGBHXLQHYRLJXYSWXWXZSLDFHLSLYNJLZYFLYJYCDRJLFSYZFSLLCQYQFGJYHYXZLYLMSTDJCYHBZLLNWLXXYGYYHSMGDHXXHHLZZJZXCZZZCYQZFNGWPYLCPKPYYPMCLQKDGXZGGWQBDXZZKZFBXXLZXJTPJPTTBYTSZZDWSLCHZHSLTYXHQLHYXXXYYZYSWTXZKHLXZXZPYHGCHKCFSYHUTJRLXFJXPTZTWHPLYXFCRHXSHXKYXXYHZQDXQWULHYHMJTBFLKHTXCWHJFWJCFPQRYQXCYYYQYGRPYWSGSUNGWCHKZDXYFLXXHJJBYZWTSXXNCYJJYMSWZJQRMHXZWFQSYLZJZGBHYNSLBGTTCSYBYXXWXYHXYYXNSQYXMQYWRGYQLXBBZLJSYLPSYTJZYHYZAWLRORJMKSCZJXXXYXCHDYXRYXXJDTSQFXLYLTSFFYXLMTYJMJUYYYXLTZCSXQZQHZXLYYXZHDNBRXXXJCTYHLBRLMBRLLAXKYLLLJLYXXLYCRYLCJTGJCMTLZLLCYZZPZPCYAWHJJFYBDYYZSMPCKZDQYQPBPCJPDCYZMDPBCYYDYCNNPLMTMLRMFMMGWYZBSJGYGSMZQQQZTXMKQWGXLLPJGZBQCDJJJFPKJKCXBLJMSWMDTQJXLDLPPBXCWRCQFBFQJCZAHZGMYKPHYYHZYKNDKZMBPJYXPXYHLFPNYYGXJDBKXNXHJMZJXSTRSTLDXSKZYSYBZXJLXYSLBZYSLHXJPFXPQNBYLLJQKYGZMCYZZYMCCSLCLHZFWFWYXZMWSXTYNXJHPYYMCYSPMHYSMYDYSHQYZCHMJJMZCAAGCFJBBHPLYZYLXXSDJGXDHKXXTXXNBHRMLYJSLTXMRHNLXQJXYZLLYSWQGDLBJHDCGJYQYCMHWFMJYBMBYJYJWYMDPWHXQLDYGPDFXXBCGJSPCKRSSYZJMSLBZZJFLJJJLGXZGYXYXLSZQYXBEXYXHGCXBPLDYHWETTWWCJMBTXCHXYQXLLXFLYXLLJLSSFWDPZSMYJCLMWYTCZPCHQEKCQBWLCQYDPLQPPQZQFJQDJHYMMCXTXDRMJWRHXCJZYLQXDYYNHYYHRSLSRSYWWZJYMTLTLLGTQCJZYABTCKZCJYCCQLJZQXALMZYHYWLWDXZXQDLLQSHGPJFJLJHJABCQZDJGTKHSSTCYJLPSWZLXZXRWGLDLZRLZXTGSLLLLZLYXXWGDZYGBDPHZPBRLWSXQBPFDWOFMWHLYPCBJCCLDMBZPBZZLCYQXLDOMZBLZWPDWYYGDSTTHCSQSCCRSSSYSLFYBFNTYJSZDFNDPDHDZZMBBLSLCMYFFGTJJQWFTMTPJWFNLBZCMMJTGBDZLQLPYFHYYMJYLSDCHDZJWJCCTLJCLDTLJJCPDDSQDSSZYBNDBJLGGJZXSXNLYCYBJXQYCBYLZCFZPPGKCXZDZFZTJJFJSJXZBNZYJQTTYJYHTYCZHYMDJXTTMPXSPLZCDWSLSHXYPZGTFMLCJTYCBPMGDKWYCYZCDSZZYHFLYCTYGWHKJYYLSJCXGYWJCBLLCSNDDBTZBSCLYZCZZSSQDLLMQYYHFSLQLLXFTYHABXGWNYWYYPLLSDLDLLBJCYXJZMLHLJDXYYQYTDLLLBUGBFDFBBQJZZMDPJHGCLGMJJPGAEHHBWCQXAXHHHZCHXYPHJAXHLPHJPGPZJQCQZGJJZZUZDMQYYBZZPHYHYBWHAZYJHYKFGDPFQSDLZMLJXKXGALXZDAGLMDGXMWZQYXXDXXPFDMMSSYMPFMDMMKXKSYZYSHDZKXSYSMMZZZMSYDNZZCZXFPLSTMZDNMXCKJMZTYYMZMZZMSXHHDCZJEMXXKLJSTLWLSQLYJZLLZJSSDPPMHNLZJCZYHMXXHGZCJMDHXTKGRMXFWMCGMWKDTKSXQMMMFZZYDKMSCLCMPCGMHSPXQPZDSSLCXKYXTWLWJYAHZJGZQMCSNXYYMMPMLKJXMHLMLQMXCTKZMJQYSZJSYSZHSYJZJCDAJZYBSDQJZGWZQQXFKDMSDJLFWEHKZQKJPEYPZYSZCDWYJFFMZZYLTTDZZEFMZLBNPPLPLPEPSZALLTYLKCKQZKGENQLWAGYXYDPXLHSXQQWQCQXQCLHYXXMLYCCWLYMQYSKGCHLCJNSZKPYZKCQZQLJPDMDZHLASXLBYDWQLWDNBQCRYDDZTJYBKBWSZDXDTNPJDTCTQDFXQQMGNXECLTTBKPWSLCTYQLPWYZZKLPYGZCQQPLLKCCYLPQMZCZQCLJSLQZDJXLDDHPZQDLJJXZQDXYZQKZLJCYQDYJPPYPQYKJYRMPCBYMCXKLLZLLFQPYLLLMBSGLCYSSLRSYSQTMXYXZQZFDZUYSYZTFFMZZSMZQHZSSCCMLYXWTPZGXZJGZGSJSGKDDHTQGGZLLBJDZLCBCHYXYZHZFYWXYZYMSDBZZYJGTSMTFXQYXQSTDGSLNXDLRYZZLRYYLXQHTXSRTZNGZXBNQQZFMYKMZJBZYMKBPNLYZPBLMCNQYZZZSJZHJCTZKHYZZJRDYZHNPXGLFZTLKGJTCTSSYLLGZRZBBQZZKLPKLCZYSSUYXBJFPNJZZXCDWXZYJXZZDJJKGGRSRJKMSMZJLSJYWQSKYHQJSXPJZZZLSNSHRNYPZTWCHKLPSRZLZXYJQXQKYSJYCZTLQZYBBYBWZPQDWWYZCYTJCJXCKCWDKKZXSGKDZXWWYYJQYYTCYTDLLXWKCZKKLCCLZCQQDZLQLCSFQCHQHSFSMQZZLNBJJZBSJHTSZDYSJQJPDLZCDCWJKJZZLPYCGMZWDJJBSJQZSYZYHHXJPBJYDSSXDZNCGLQMBTSFSBPDZDLZNFGFJGFSMPXJQLMBLGQCYYXBQKDJJQYRFKZTJDHCZKLBSDZCFJTPLLJGXHYXZCSSZZXSTJYGKGCKGYOQXJPLZPBPGTGYJZGHZQZZLBJLSQFZGKQQJZGYCZBZQTLDXRJXBSXXPZXHYZYCLWDXJJHXMFDZPFZHQHQMQGKSLYHTYCGFRZGNQXCLPDLBZCSCZQLLJBLHBZCYPZZPPDYMZZSGYHCKCPZJGSLJLNSCDSLDLXBMSTLDDFJMKDJDHZLZXLSZQPQPGJLLYBDSZGQLBZLSLKYYHZTTNTJYQTZZPSZQZTLLJTYYLLQLLQYZQLBDZLSLYYZYMDFSZSNHLXZNCZQZPBWSKRFBSYZMTHBLGJPMCZZLSTLXSHTCSYZLZBLFEQHLXFLCJLYLJQCBZLZJHHSSTBRMHXZHJZCLXFNBGXGTQJCZTMSFZKJMSSNXLJKBHSJXNTNLZDNTLMSJXGZJYJCZXYJYJWRWWQNZTNFJSZPZSHZJFYRDJSFSZJZBJFZQZZHZLXFYSBZQLZSGYFTZDCSZXZJBQMSZKJRHYJZCKMJKHCHGTXKXQGLXPXFXTRTYLXJXHDTSJXHJZJXZWZLCQSBTXWXGXTXXHXFTSDKFJHZYJFJXRZSDLLLTQSQQZQWZXSYQTWGWBZCGZLLYZBCLMQQTZHZXZXLJFRMYZFLXYSQXXJKXRMQDZDMMYYBSQBHGZMWFWXGMXLZPYYTGZYCCDXYZXYWGSYJYZNBHPZJSQSYXSXRTFYZGRHZTXSZZTHCBFCLSYXZLZQMZLMPLMXZJXSFLBYZMYQHXJSXRXSQZZZSSLYFRCZJRCRXHHZXQYDYHXSJJHZCXZBTYNSYSXJBQLPXZQPYMLXZKYXLXCJLCYSXXZZLXDLLLJJYHZXGYJWKJRWYHCPSGNRZLFZWFZZNSXGXFLZSXZZZBFCSYJDBRJKRDHHGXJLJJTGXJXXSTJTJXLYXQFCSGSWMSBCTLQZZWLZZKXJMLTMJYHSDDBXGZHDLBMYJFRZFSGCLYJBPMLYSMSXLSZJQQHJZFXGFQFQBPXZGYYQXGZTCQWYLTLGWSGWHRLFSFGZJMGMGBGTJFSYZZGZYZAFLSSPMLPFLCWBJZCLJJMZLPJJLYMQDMYYYFBGYGYZMLYZDXQYXRQQQHSYYYQXYLJTYXFSFSLLGNQCYHYCWFHCCCFXPYLYPLLZYXXXXXKQHHXSHJZCFZSCZJXCPZWHHHHHAPYLQALPQAFYHXDYLUKMZQGGGDDESRNNZLTZGCHYPPYSQJJHCLLJTOLNJPZLJLHYMHEYDYDSQYCDDHGZUNDZCLZYZLLZNTNYZGSLHSLPJJBDGWXPCDUTJCKLKCLWKLLCASSTKZZDNQNTTLYYZSSYSSZZRYLJQKCQDHHCRXRZYDGRGCWCGZQFFFPPJFZYNAKRGYWYQPQXXFKJTSZZXSWZDDFBBXTBGTZKZNPZZPZXZPJSZBMQHKCYXYLDKLJNYPKYGHGDZJXXEAHPNZKZTZCMXCXMMJXNKSZQNMNLWBWWXJKYHCPSTMCSQTZJYXTPCTPDTNNPGLLLZSJLSPBLPLQHDTNJNLYYRSZFFJFQWDPHZDWMRZCCLODAXNSSNYZRESTYJWJYJDBCFXNMWTTBYLWSTSZGYBLJPXGLBOCLHPCBJLTMXZLJYLZXCLTPNCLCKXTPZJSWCYXSFYSZDKNTLBYJCYJLLSTGQCBXRYZXBXKLYLHZLQZLNZCXWJZLJZJNCJHXMNZZGJZZXTZJXYCYYCXXJYYXJJXSSSJSTSSTTPPGQTCSXWZDCSYFPTFBFHFBBLZJCLZZDBXGCXLQPXKFZFLSYLTUWBMQJHSZBMDDBCYSCCLDXYCDDQLYJJWMQLLCSGLJJSYFPYYCCYLTJANTJJPWYCMMGQYYSXDXQMZHSZXPFTWWZQSWQRFKJLZJQQYFBRXJHHFWJJZYQAZMYFRHCYYBYQWLPEXCCZSTYRLTTDMQLYKMBBGMYYJPRKZNPBSXYXBHYZDJDNGHPMFSGMWFZMFQMMBCMZZCJJLCNUXYQLMLRYGQZCYXZLWJGCJCGGMCJNFYZZJHYCPRRCMTZQZXHFQGTJXCCJEAQCRJYHPLQLSZDJRBCQHQDYRHYLYXJSYMHZYDWLDFRYHBPYDTSSCNWBXGLPZMLZZTQSSCPJMXXYCSJYTYCGHYCJWYRXXLFEMWJNMKLLSWTXHYYYNCMMCWJDQDJZGLLJWJRKHPZGGFLCCSCZMCBLTBHBQJXQDSPDJZZGKGLFQYWBZYZJLTSTDHQHCTCBCHFLQMPWDSHYYTQWCNZZJTLBYMBPDYYYXSQKXWYYFLXXNCWCXYPMAELYKKJMZZZBRXYYQJFLJPFHHHYTZZXSGQQMHSPGDZQWBWPJHZJDYSCQWZKTXXSQLZYYMYSDZGRXCKKUJLWPYSYSCSYZLRMLQSYLJXBCXTLWDQZPCYCYKPPPNSXFYZJJRCEMHSZMSXLXGLRWGCSTLRSXBZGBZGZTCPLUJLSLYLYMTXMTZPALZXPXJTJWTCYYZLBLXBZLQMYLXPGHDSLSSDMXMBDZZSXWHAMLCZCPJMCNHJYSNSYGCHSKQMZZQDLLKABLWJXSFMOCDXJRRLYQZKJMYBYQLYHETFJZFRFKSRYXFJTWDSXXSYSQJYSLYXWJHSNLXYYXHBHAWHHJZXWMYLJCSSLKYDZTXBZSYFDXGXZJKHSXXYBSSXDPYNZWRPTQZCZENYGCXQFJYKJBZMLJCMQQXUOXSLYXXLYLLJDZBTYMHPFSTTQQWLHOKYBLZZALZXQLHZWRRQHLSTMYPYXJJXMQSJFNBXYXYJXXYQYLTHYLQYFMLKLJTMLLHSZWKZHLJMLHLJKLJSTLQXYLMBHHLNLZXQJHXCFXXLHYHJJGBYZZKBXSCQDJQDSUJZYYHZHHMGSXCSYMXFEBCQWWRBPYYJQTYZCYQYQQZYHMWFFHGZFRJFCDPXNTQYZPDYKHJLFRZXPPXZDBBGZQSTLGDGYLCQMLCHHMFYWLZYXKJLYPQHSYWMQQGQZMLZJNSQXJQSYJYCBEHSXFSZPXZWFLLBCYYJDYTDTHWZSFJMQQYJLMQXXLLDTTKHHYBFPWTYYSQQWNQWLGWDEBZWCMYGCULKJXTMXMYJSXHYBRWFYMWFRXYQMXYSZTZZTFYKMLDHQDXWYYNLCRYJBLPSXCXYWLSPRRJWXHQYPHTYDNXHHMMYWYTZCSQMTSSCCDALWZTCPQPYJLLQZYJSWXMZZMMYLMXCLMXCZMXMZSQTZPPQQBLPGXQZHFLJJHYTJSRXWZXSCCDLXTYJDCQJXSLQYCLZXLZZXMXQRJMHRHZJBHMFLJLMLCLQNLDXZLLLPYPSYJYSXCQQDCMQJZZXHNPNXZMEKMXHYKYQLXSXTXJYYHWDCWDZHQYYBGYBCYSCFGPSJNZDYZZJZXRZRQJJYMCANYRJTLDPPYZBSTJKXXZYPFDWFGZZRPYMTNGXZQBYXNBUFNQKRJQZMJEGRZGYCLKXZDSKKNSXKCLJSPJYYZLQQJYBZSSQLLLKJXTBKTYLCCDDBLSPPFYLGYDTZJYQGGKQTTFZXBDKTYYHYBBFYTYYBCLPDYTGDHRYRNJSPTCSNYJQHKLLLZSLYDXXWBCJQSPXBPJZJCJDZFFXXBRMLAZHCSNDLBJDSZBLPRZTSWSBXBCLLXXLZDJZSJPYLYXXYFTFFFBHJJXGBYXJPMMMPSSJZJMTLYZJXSWXTYLEDQPJMYGQZJGDJLQJWJQLLSJGJGYGMSCLJJXDTYGJQJQJCJZCJGDZZSXQGSJGGCXHQXSNQLZZBXHSGZXCXYLJXYXYYDFQQJHJFXDHCTXJYRXYSQTJXYEFYYSSYYJXNCYZXFXMSYSZXYYSCHSHXZZZGZZZGFJDLTYLNPZGYJYZYYQZPBXQBDZTZCZYXXYHHSQXSHDHGQHJHGYWSZTMZMLHYXGEBTYLZKQWYTJZRCLEKYSTDBCYKQQSAYXCJXWWGSBHJYZYDHCSJKQCXSWXFLTYNYZPZCCZJQTZWJQDZZZQZLJJXLSBHPYXXPSXSHHEZTXFPTLQYZZXHYTXNCFZYYHXGNXMYWXTZSJPTHHGYMXMXQZXTSBCZYJYXXTYYZYPCQLMMSZMJZZLLZXGXZAAJZYXJMZXWDXZSXZDZXLEYJJZQBHZWZZZQTZPSXZTDSXJJJZNYAZPHXYYSRNQDTHZHYYKYJHDZXZLSWCLYBZYECWCYCRYLCXNHZYDZYDYJDFRJJHTRSQTXYXJRJHOJYNXELXSFSFJZGHPZSXZSZDZCQZBYYKLSGSJHCZSHDGQGXYZGXCHXZJWYQWGYHKSSEQZZNDZFKWYSSTCLZSTSYMCDHJXXYWEYXCZAYDMPXMDSXYBSQMJMZJMTZQLPJYQZCGQHXJHHLXXHLHDLDJQCLDWBSXFZZYYSCHTYTYYBHECXHYKGJPXHHYZJFXHWHBDZFYZBCAPNPGNYDMSXHMMMMAMYNBYJTMPXYYMCTHJBZYFCGTYHWPHFTWZZEZSBZEGPFMTSKFTYCMHFLLHGPZJXZJGZJYXZSBBQSCZZLZCCSTPGXMJSFTCCZJZDJXCYBZLFCJSYZFGSZLYBCWZZBYZDZYPSWYJZXZBDSYUXLZZBZFYGCZXBZHZFTPBGZGEJBSTGKDMFHYZZJHZLLZZGJQZLSFDJSSCBZGPDLFZFZSZYZYZSYGCXSNXXCHCZXTZZLJFZGQSQYXZJQDCCZTQCDXZJYQJQCHXZTDLGSCXZSYQJQTZWLQDQZTQCHQQJZYEZZZPBWKDJFCJPZTYPQYQTTYNLMBDKTJZPQZQZZFPZSBNJLGYJDXJDZZKZGQKXDLPZJTCJDQBXDJQJSTCKNXBXZMSLYJCQMTJQWWCJQNJNLLLHJCWQTBZQYDZCZPZZDZYDDCYZZZCCJTTJFZDPRRTZTJDCQTQZDTJNPLZBCLLCTZSXKJZQZPZLBZRBTJDCXFCZDBCCJJLTQQPLDCGZDBBZJCQDCJWYNLLZYZCCDWLLXWZLXRXNTQQCZXKQLSGDFQTDDGLRLAJJTKUYMKQLLTZYTDYYCZGJWYXDXFRSKSTQTENQMRKQZHHQKDLDAZFKYPBGGPZREBZZYKZZSPEGJXGYKQZZZSLYSYYYZWFQZYLZZLZHWCHKYPQGNPGBLPLRRJYXCCSYYHSFZFYBZYYTGZXYLXCZWXXZJZBLFFLGSKHYJZEYJHLPLLLLCZGXDRZELRHGKLZZYHZLYQSZZJZQLJZFLNBHGWLCZCFJYSPYXZLZLXGCCPZBLLCYBBBBUBBCBPCRNNZCZYRBFSRLDCGQYYQXYGMQZWTZYTYJXYFWTEHZZJYWLCCNTZYJJZDEDPZDZTSYQJHDYMBJNYJZLXTSSTPHNDJXXBYXQTZQDDTJTDYYTGWSCSZQFLSHLGLBCZPHDLYZJYCKWTYTYLBNYTSDSYCCTYSZYYEBHEXHQDTWNYGYCLXTSZYSTQMYGZAZCCSZZDSLZCLZRQXYYELJSBYMXSXZTEMBBLLYYLLYTDQYSHYMRQWKFKBFXNXSBYCHXBWJYHTQBPBSBWDZYLKGZSKYHXQZJXHXJXGNLJKZLYYCDXLFYFGHLJGJYBXQLYBXQPQGZTZPLNCYPXDJYQYDYMRBESJYYHKXXSTMXRCZZYWXYQYBMCLLYZHQYZWQXDBXBZWZMSLPDMYSKFMZKLZCYQYCZLQXFZZYDQZPZYGYJYZMZXDZFYFYTTQTZHGSPCZMLCCYTZXJCYTJMKSLPZHYSNZLLYTPZCTZZCKTXDHXXTQCYFKSMQCCYYAZHTJPCYLZLYJBJXTPNYLJYYNRXSYLMMNXJSMYBCSYSYLZYLXJJQYLDZLPQBFZZBLFNDXQKCZFYWHGQMRDSXYCYTXNQQJZYYPFZXDYZFPRXEJDGYQBXRCNFYYQPGHYJDYZXGRHTKYLNWDZNTSMPKLBTHBPYSZBZTJZSZZJTYYXZPHSSZZBZCZPTQFZMYFLYPYBBJQXZMXXDJMTSYSKKBJZXHJCKLPSMKYJZCXTMLJYXRZZQSLXXQPYZXMKYXXXJCLJPRMYYGADYSKQLSNDHYZKQXZYZTCGHZTLMLWZYBWSYCTBHJHJFCWZTXWYTKZLXQSHLYJZJXTMPLPYCGLTBZZTLZJCYJGDTCLKLPLLQPJMZPAPXYZLKKTKDZCZZBNZDYDYQZJYJGMCTXLTGXSZLMLHBGLKFWNWZHDXUHLFMKYSLGXDTWWFRJEJZTZHYDXYKSHWFZCQSHKTMQQHTZHYMJDJSKHXZJZBZZXYMPAGQMSTPXLSKLZYNWRTSQLSZBPSPSGZWYHTLKSSSWHZZLYYTNXJGMJSZSUFWNLSOZTXGXLSAMMLBWLDSZYLAKQCQCTMYCFJBSLXCLZZCLXXKSBZQCLHJPSQPLSXXCKSLNHPSFQQYTXYJZLQLDXZQJZDYYDJNZPTUZDSKJFSLJHYLZSQZLBTXYDGTQFDBYAZXDZHZJNHHQBYKNXJJQCZMLLJZKSPLDYCLBBLXKLELXJLBQYCXJXGCNLCQPLZLZYJTZLJGYZDZPLTQCSXFDMNYCXGBTJDCZNBGBQYQJWGKFHTNPYQZQGBKPBBYZMTJDYTBLSQMPSXTBNPDXKLEMYYCJYNZCTLDYKZZXDDXHQSHDGMZSJYCCTAYRZLPYLTLKXSLZCGGEXCLFXLKJRTLQJAQZNCMBYDKKCXGLCZJZXJHPTDJJMZQYKQSECQZDSHHADMLZFMMZBGNTJNNLGBYJBRBTMLBYJDZXLCJLPLDLPCQDHLXZLYCBLCXZZJADJLNZMMSSSMYBHBSQKBHRSXXJMXSDZNZPXLGBRHWGGFCXGMSKLLTSJYYCQLTSKYWYYHYWXBXQYWPYWYKQLSQPTNTKHQCWDQKTWPXXHCPTHTWUMSSYHBWCRWXHJMKMZNGWTMLKFGHKJYLSYYCXWHYECLQHKQHTTQKHFZLDXQWYZYYDESBPKYRZPJFYYZJCEQDZZDLATZBBFJLLCXDLMJSSXEGYGSJQXCWBXSSZPDYZCXDNYXPPZYDLYJCZPLTXLSXYZYRXCYYYDYLWWNZSAHJSYQYHGYWWAXTJZDAXYSRLTDPSSYYFNEJDXYZHLXLLLZQZSJNYQYQQXYJGHZGZCYJCHZLYCDSHWSHJZYJXCLLNXZJJYYXNFXMWFPYLCYLLABWDDHWDXJMCXZTZPMLQZHSFHZYNZTLLDYWLSLXHYMMYLMBWWKYXYADTXYLLDJPYBPWUXJMWMLLSAFDLLYFLBHHHBQQLTZJCQJLDJTFFKMMMBYTHYGDCQRDDWRQJXNBYSNWZDBYYTBJHPYBYTTJXAAHGQDQTMYSTQXKBTZPKJLZRBEQQSSMJJBDJOTGTBXPGBKTLHQXJJJCTHXQDWJLWRFWQGWSHCKRYSWGFTGYGBXSDWDWRFHWYTJJXXXJYZYSLPYYYPAYXHYDQKXSHXYXGSKQHYWFDDDPPLCJLQQEEWXKSYYKDYPLTJTHKJLTCYYHHJTTPLTZZCDLTHQKZXQYSTEEYWYYZYXXYYSTTJKLLPZMCYHQGXYHSRMBXPLLNQYDQHXSXXWGDQBSHYLLPJJJTHYJKYPPTHYYKTYEZYENMDSHLCRPQFDGFXZPSFTLJXXJBSWYYSKSFLXLPPLBBBLBSFXFYZBSJSSYLPBBFFFFSSCJDSTZSXZRYYSYFFSYZYZBJTBCTSBSDHRTJJBYTCXYJEYLXCBNEBJDSYXYKGSJZBXBYTFZWGENYHHTHZHHXFWGCSTBGXKLSXYWMTMBYXJSTZSCDYQRCYTWXZFHMYMCXLZNSDJTTTXRYCFYJSBSDYERXJLJXBBDEYNJGHXGCKGSCYMBLXJMSZNSKGXFBNBPTHFJAAFXYXFPXMYPQDTZCXZZPXRSYWZDLYBBKTYQPQJPZYPZJZNJPZJLZZFYSBTTSLMPTZRTDXQSJEHBZYLZDHLJSQMLHTXTJECXSLZZSPKTLZKQQYFSYGYWPCPQFHQHYTQXZKRSGTTSQCZLPTXCDYYZXSQZSLXLZMYCPCQBZYXHBSXLZDLTCDXTYLZJYYZPZYZLTXJSJXHLPMYTXCQRBLZSSFJZZTNJYTXMYJHLHPPLCYXQJQQKZZSCPZKSWALQSBLCCZJSXGWWWYGYKTJBBZTDKHXHKGTGPBKQYSLPXPJCKBMLLXDZSTBKLGGQKQLSBKKTFXRMDKBFTPZFRTBBRFERQGXYJPZSSTLBZTPSZQZSJDHLJQLZBPMSMMSXLQQNHKNBLRDDNXXDHDDJCYYGYLXGZLXSYGMQQGKHBPMXYXLYTQWLWGCPBMQXCYZYDRJBHTDJYHQSHTMJSBYPLWHLZFFNYPMHXXHPLTBQPFBJWQDBYGPNZTPFZJGSDDTQSHZEAWZZYLLTYYBWJKXXGHLFKXDJTMSZSQYNZGGSWQSPHTLSSKMCLZXYSZQZXNCJDQGZDLFNYKLJCJLLZLMZZNHYDSSHTHZZLZZBBHQZWWYCRZHLYQQJBEYFXXXWHSRXWQHWPSLMSSKZTTYGYQQWRSLALHMJTQJSMXQBJJZJXZYZKXBYQXBJXSHZTSFJLXMXZXFGHKZSZGGYLCLSARJYHSLLLMZXELGLXYDJYTLFBHBPNLYZFBBHPTGJKWETZHKJJXZXXGLLJLSTGSHJJYQLQZFKCGNNDJSSZFDBCTWWSEQFHQJBSAQTGYPQLBXBMMYWXGSLZHGLZGQYFLZBYFZJFRYSFMBYZHQGFWZSYFYJJPHZBYYZFFWODGRLMFTWLBZGYCQXCDJYGZYYYYTYTYDWEGAZYHXJLZYYHLRMGRXXZCLHNELJJTJTPWJYBJJBXJJTJTEEKHWSLJPLPSFYZPQQBDLQJJTYYQLYZKDKSQJYYQZLDQTGJQYZJSUCMRYQTHTEJMFCTYHYPKMHYZWJDQFHYYXWSHCTXRLJHQXHCCYYYJLTKTTYTMXGTCJTZAYYOCZLYLBSZYWJYTSJYHBYSHFJLYGJXXTMZYYLTXXYPZLXYJZYZYYPNHMYMDYYLBLHLSYYQQLLNJJYMSOYQBZGDLYXYLCQYXTSZEGXHZGLHWBLJHEYXTWQMAKBPQCGYSHHEGQCMWYYWLJYJHYYZLLJJYLHZYHMGSLJLJXCJJYCLYCJPCPZJZJMMYLCQLNQLJQJSXYJMLSZLJQLYCMMHCFMMFPQQMFYLQMCFFQMMMMHMZNFHHJGTTHHKHSLNCHHYQDXTMMQDCYZYXYQMYQYLTDCYYYZAZZCYMZYDLZFFFMMYCQZWZZMABTBYZTDMNZZGGDFTYPCGQYTTSSFFWFDTZQSSYSTWXJHXYTSXXYLBYQHWWKXHZXWZNNZZJZJJQJCCCHYYXBZXZCYZTLLCQXYNJYCYYCYNZZQYYYEWYCZDCJYCCHYJLBTZYYCQWMPWPYMLGKDLDLGKQQBGYCHJXY";
- var id = $(this).attr('id'), by, other,
- table = $(this).closest('table'),
- repo_items = $('tr:gt(0)', table);
-
- if (id.indexOf('up') != -1) {
- by = function(a, b) { return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1 };
- other = $('#' + id.replace('up', 'down'));
- } else {
- by = function(a, b) { return a.name.toLowerCase() < b.name.toLowerCase() ? 1 : -1 };
- other = $('#' + id.replace('down', 'up'));
- }
- var name_list = [], cn_name_list = [];
- repo_items.each(function() {
- var name = $(this).attr('data-repo_name');
- //get unicode
- var uni = name.charCodeAt(0);
- //not a Chinese character
- if (uni > 40869 || uni < 19968) {
- name_list.push({'name': name, 'element': this});
- } else {
- //get Chinese character's PinYin
- cn_name_list.push({'name': strChineseFirstPY.charAt(uni - 19968), 'element': this});
- }
- });
-
- name_list.sort(by);
- cn_name_list.sort(by);
- repo_items.detach();
-
- if (id.indexOf('up') != -1) {
- $(name_list).each(function(index, item) {
- table.append(item.element);
- });
- $(cn_name_list).each(function(index, item) {
- table.append(item.element);
- });
- } else {
- $(cn_name_list).each(function(index, item) {
- table.append(item.element);
- });
- $(name_list).each(function(index, item) {
- table.append(item.element);
- });
- }
- $(this).addClass('hide');
- other.removeClass('hide');
-};
-
-function sort_lib_by_time() {
- var id = $(this).attr('id'), by, other, sort_list = [],
- table = $(this).closest('table'),
- repo_items = $('tr:gt(0)', table);
-
- if (id.indexOf('up') != -1) {
- by = function(a, b) { return a.time < b.time ? -1 : 1 };
- other = $('#' + id.replace('up', 'down'));
- } else {
- by = function(a, b) { return a.time < b.time ? 1 : -1 };
- other = $('#' + id.replace('down', 'up'));
- }
-
- repo_items.each(function() {
- sort_list.push({'time':$(this).data('time'), 'element':this});
- });
-
- sort_list.sort(by);
-
- repo_items.detach();
-
- $(sort_list).each(function(index, item) {
- table.append(item.element);
- });
-
- $(this).addClass('hide');
- other.removeClass('hide');
-}
diff --git a/seahub/api2/endpoints/dir_shared_items.py b/seahub/api2/endpoints/dir_shared_items.py
index 1253e285f3..0bb0bf3e96 100644
--- a/seahub/api2/endpoints/dir_shared_items.py
+++ b/seahub/api2/endpoints/dir_shared_items.py
@@ -423,6 +423,10 @@ class DirSharedItemsEndpoint(APIView):
if shared_to is None or not is_valid_username(shared_to):
return api_error(status.HTTP_400_BAD_REQUEST, 'Email %s invalid.' % shared_to)
+ # if user not found, permission will be None
+ permission = seafile_api.check_permission_by_path(
+ shared_repo.id, '/', shared_to)
+
if is_org_context(request):
org_id = request.user.org.org_id
seaserv.seafserv_threaded_rpc.org_remove_share(
@@ -430,9 +434,6 @@ class DirSharedItemsEndpoint(APIView):
else:
seaserv.remove_share(shared_repo.id, username, shared_to)
- # if user not found, permission will be None
- permission = seafile_api.check_permission_by_path(repo.id, path,
- shared_to)
send_perm_audit_msg('delete-repo-perm', username, shared_to,
repo_id, path, permission)
@@ -445,7 +446,15 @@ class DirSharedItemsEndpoint(APIView):
# hacky way to get group repo permission
permission = ''
- for e in seafile_api.list_repo_shared_group_by_user(username, shared_repo.id):
+ if is_org_context(request):
+ org_id = request.user.org.org_id
+ shared_groups = seafile_api.list_org_repo_shared_group(
+ org_id, username, shared_repo.id)
+ else:
+ shared_groups = seafile_api.list_repo_shared_group(
+ username, shared_repo.id)
+
+ for e in shared_groups:
if e.group_id == group_id:
permission = e.perm
break
diff --git a/seahub/api2/endpoints/share_links.py b/seahub/api2/endpoints/share_links.py
index 7e2b775fed..4cf510ad9b 100644
--- a/seahub/api2/endpoints/share_links.py
+++ b/seahub/api2/endpoints/share_links.py
@@ -1,3 +1,4 @@
+import os
import logging
from constance import config
from dateutil.relativedelta import relativedelta
@@ -21,6 +22,7 @@ from seahub.api2.throttling import UserRateThrottle
from seahub.share.models import FileShare, OrgFileShare
from seahub.utils import gen_shared_link, is_org_context
from seahub.views import check_folder_permission
+from seahub.utils.timeutils import datetime_to_isoformat_timestr
logger = logging.getLogger(__name__)
@@ -29,15 +31,43 @@ def get_share_link_info(fileshare):
data = {}
token = fileshare.token
- data['repo_id'] = fileshare.repo_id
- data['path'] = fileshare.path
- data['ctime'] = fileshare.ctime
- data['view_cnt'] = fileshare.view_cnt
- data['link'] = gen_shared_link(token, fileshare.s_type)
- data['token'] = token
- data['expire_date'] = fileshare.expire_date
- data['is_expired'] = fileshare.is_expired()
+ repo_id = fileshare.repo_id
+ try:
+ repo = seafile_api.get_repo(repo_id)
+ except Exception as e:
+ logger.error(e)
+ repo = None
+
+ path = fileshare.path
+ if path:
+ obj_name = '/' if path == '/' else os.path.basename(path.rstrip('/'))
+ else:
+ obj_name = ''
+
+ if fileshare.expire_date:
+ expire_date = datetime_to_isoformat_timestr(fileshare.expire_date)
+ else:
+ expire_date = ''
+
+ if fileshare.ctime:
+ ctime = datetime_to_isoformat_timestr(fileshare.ctime)
+ else:
+ ctime = ''
+
data['username'] = fileshare.username
+ data['repo_id'] = repo_id
+ data['repo_name'] = repo.repo_name if repo else ''
+
+ data['path'] = path
+ data['obj_name'] = obj_name
+ data['is_dir'] = True if fileshare.s_type == 'd' else False
+
+ data['token'] = token
+ data['link'] = gen_shared_link(token, fileshare.s_type)
+ data['view_cnt'] = fileshare.view_cnt
+ data['ctime'] = ctime
+ data['expire_date'] = expire_date
+ data['is_expired'] = fileshare.is_expired()
return data
@@ -64,14 +94,20 @@ class ShareLinks(APIView):
return (None, None)
def get(self, request):
- """ get share links.
+ """ Get all share links of a user.
+
+ Permission checking:
+ 1. default(NOT guest) user;
"""
if not self._can_generate_shared_link(request):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
- # check if args invalid
+ # get all share links
+ username = request.user.username
+ fileshares = FileShare.objects.filter(username=username)
+
repo_id = request.GET.get('repo_id', None)
if repo_id:
repo = seafile_api.get_repo(repo_id)
@@ -79,81 +115,91 @@ class ShareLinks(APIView):
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
- # repo level permission check
- if not check_folder_permission(request, repo_id, '/'):
- error_msg = 'Permission denied.'
- return api_error(status.HTTP_403_FORBIDDEN, error_msg)
-
- path = request.GET.get('path', None)
- if path:
- try:
- obj_id, s_type = self._generate_obj_id_and_type_by_path(repo_id, path)
- except SearpcError as e:
- logger.error(e)
- error_msg = 'Internal Server Error'
- return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
-
- if not obj_id:
- if s_type == 'f':
- error_msg = 'file %s not found.' % path
- elif s_type == 'd':
- error_msg = 'folder %s not found.' % path
- else:
- error_msg = 'path %s not found.' % path
-
- return api_error(status.HTTP_404_NOT_FOUND, error_msg)
-
- # folder/path permission check
- if not check_folder_permission(request, repo_id, path):
- error_msg = 'Permission denied.'
- return api_error(status.HTTP_403_FORBIDDEN, error_msg)
-
- username = request.user.username
- fileshares = FileShare.objects.filter(username=username)
-
- # filter result by args
- if repo_id:
+ # filter share links by repo
fileshares = filter(lambda fs: fs.repo_id == repo_id, fileshares)
- if path:
- if s_type == 'd' and path[-1] != '/':
- path = path + '/'
+ path = request.GET.get('path', None)
+ if path:
+ try:
+ obj_id, s_type = self._generate_obj_id_and_type_by_path(repo_id, path)
+ except SearpcError as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
- fileshares = filter(lambda fs: fs.path == path, fileshares)
+ if not obj_id:
+ if s_type == 'f':
+ error_msg = 'file %s not found.' % path
+ elif s_type == 'd':
+ error_msg = 'folder %s not found.' % path
+ else:
+ error_msg = 'path %s not found.' % path
- result = []
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ # if path invalid, filter share links by repo
+ if s_type == 'd' and path[-1] != '/':
+ path = path + '/'
+
+ fileshares = filter(lambda fs: fs.path == path, fileshares)
+
+ links_info = []
for fs in fileshares:
link_info = get_share_link_info(fs)
- result.append(link_info)
+ links_info.append(link_info)
- if len(result) == 1:
- result = result[0]
+ if len(links_info) == 1:
+ result = links_info
+ else:
+ dir_list = filter(lambda x: x['is_dir'], links_info)
+ file_list = filter(lambda x: not x['is_dir'], links_info)
+
+ dir_list.sort(lambda x, y: cmp(x['obj_name'], y['obj_name']))
+ file_list.sort(lambda x, y: cmp(x['obj_name'], y['obj_name']))
+
+ result = dir_list + file_list
return Response(result)
def post(self, request):
- """ create share link.
+ """ Create share link.
+
+ Permission checking:
+ 1. default(NOT guest) user;
"""
- if not self._can_generate_shared_link(request):
- error_msg = 'Permission denied.'
- return api_error(status.HTTP_403_FORBIDDEN, error_msg)
-
+ # argument check
repo_id = request.data.get('repo_id', None)
if not repo_id:
error_msg = 'repo_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
- repo = seafile_api.get_repo(repo_id)
- if not repo:
- error_msg = 'Library %s not found.' % repo_id
- return api_error(status.HTTP_404_NOT_FOUND, error_msg)
-
path = request.data.get('path', None)
if not path:
error_msg = 'path invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+ password = request.data.get('password', None)
+ if password and len(password) < config.SHARE_LINK_PASSWORD_MIN_LENGTH:
+ error_msg = _('Password is too short.')
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ try:
+ expire_days = int(request.data.get('expire_days', 0))
+ except ValueError:
+ expire_days = 0
+
+ if expire_days <= 0:
+ expire_date = None
+ else:
+ expire_date = timezone.now() + relativedelta(days=expire_days)
+
+ # resource check
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
try:
obj_id, s_type = self._generate_obj_id_and_type_by_path(repo_id, path)
except SearpcError as e:
@@ -172,24 +218,13 @@ class ShareLinks(APIView):
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# permission check
- if not check_folder_permission(request, repo_id, path):
+ if not self._can_generate_shared_link(request):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
- password = request.data.get('password', None)
- if password and len(password) < config.SHARE_LINK_PASSWORD_MIN_LENGTH:
- error_msg = _('Password is too short.')
- return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
-
- try:
- expire_days = int(request.data.get('expire_days', 0))
- except ValueError:
- expire_days = 0
-
- if expire_days <= 0:
- expire_date = None
- else:
- expire_date = timezone.now() + relativedelta(days=expire_days)
+ if not check_folder_permission(request, repo_id, path):
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
username = request.user.username
if s_type == 'f':
@@ -197,18 +232,16 @@ class ShareLinks(APIView):
if not fs:
fs = FileShare.objects.create_file_link(username, repo_id, path,
password, expire_date)
- if is_org_context(request):
- org_id = request.user.org.org_id
- OrgFileShare.objects.set_org_file_share(org_id, fs)
elif s_type == 'd':
fs = FileShare.objects.get_dir_link_by_path(username, repo_id, path)
if not fs:
fs = FileShare.objects.create_dir_link(username, repo_id, path,
- password, expire_date)
- if is_org_context(request):
- org_id = request.user.org.org_id
- OrgFileShare.objects.set_org_file_share(org_id, fs)
+ password, expire_date)
+
+ if is_org_context(request):
+ org_id = request.user.org.org_id
+ OrgFileShare.objects.set_org_file_share(org_id, fs)
link_info = get_share_link_info(fs)
return Response(link_info)
@@ -217,24 +250,17 @@ class ShareLink(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
- throttle_classes = (UserRateThrottle, )
+ throttle_classes = (UserRateThrottle,)
def _can_generate_shared_link(self, request):
return request.user.permissions.can_generate_shared_link()
def get(self, request, token):
- try:
- fs = FileShare.objects.get(token=token)
- except FileShare.DoesNotExist:
- error_msg = 'token %s not found.' % token
- return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+ """ Get a special share link info.
- link_info = get_share_link_info(fs)
- return Response(link_info)
-
- def delete(self, request, token):
- """ delete share link.
+ Permission checking:
+ 1. default(NOT guest) user;
"""
if not self._can_generate_shared_link(request):
@@ -247,6 +273,26 @@ class ShareLink(APIView):
error_msg = 'token %s not found.' % token
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+ link_info = get_share_link_info(fs)
+ return Response(link_info)
+
+ def delete(self, request, token):
+ """ Delete share link.
+
+ Permission checking:
+ 1. default(NOT guest) user;
+ 2. link owner;
+ """
+
+ if not self._can_generate_shared_link(request):
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ try:
+ fs = FileShare.objects.get(token=token)
+ except FileShare.DoesNotExist:
+ return Response({'success': True})
+
username = request.user.username
if not fs.is_owner(username):
error_msg = 'Permission denied.'
@@ -254,8 +300,9 @@ class ShareLink(APIView):
try:
fs.delete()
- return Response({'success': True})
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ return Response({'success': True})
diff --git a/seahub/api2/endpoints/shared_folders.py b/seahub/api2/endpoints/shared_folders.py
new file mode 100644
index 0000000000..31f6f693e6
--- /dev/null
+++ b/seahub/api2/endpoints/shared_folders.py
@@ -0,0 +1,77 @@
+import logging
+
+from rest_framework.authentication import SessionAuthentication
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from rest_framework import status
+
+import seaserv
+from seaserv import seafile_api, ccnet_api
+
+from seahub.api2.utils import api_error
+from seahub.api2.authentication import TokenAuthentication
+from seahub.api2.throttling import UserRateThrottle
+
+from seahub.utils import is_org_context
+from seahub.base.templatetags.seahub_tags import email2nickname
+
+logger = logging.getLogger(__name__)
+
+class SharedFolders(APIView):
+
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated,)
+ throttle_classes = (UserRateThrottle,)
+
+ def get(self, request, format=None):
+ """ List all shared out folders.
+
+ Permission checking:
+ 1. all authenticated user can perform this action.
+ """
+
+ shared_repos = []
+ username = request.user.username
+
+ try:
+ if is_org_context(request):
+ org_id = request.user.org.org_id
+ shared_repos += seafile_api.get_org_share_out_repo_list(org_id, username, -1, -1)
+ shared_repos += seaserv.seafserv_threaded_rpc.get_org_group_repos_by_owner(org_id, username)
+ #shared_repos += seaserv.seafserv_threaded_rpc.list_org_inner_pub_repos_by_owner(org_id, username)
+ else:
+ shared_repos += seafile_api.get_share_out_repo_list(username, -1, -1)
+ shared_repos += seafile_api.get_group_repos_by_owner(username)
+ #if not request.cloud_mode:
+ #shared_repos += seaserv.list_inner_pub_repos_by_owner(username)
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ returned_result = []
+ shared_repos.sort(lambda x, y: cmp(x.repo_name, y.repo_name))
+ for repo in shared_repos:
+ if not repo.is_virtual:
+ continue
+
+ result = {}
+ result['repo_id'] = repo.origin_repo_id
+ result['path'] = repo.origin_path
+ result['folder_name'] = repo.name
+ result['share_type'] = repo.share_type
+ result['share_permission'] = repo.permission
+
+ if repo.share_type == 'personal':
+ result['user_name'] = email2nickname(repo.user)
+ result['user_email'] = repo.user
+
+ if repo.share_type == 'group':
+ group = ccnet_api.get_group(repo.group_id)
+ result['group_id'] = repo.group_id
+ result['group_name'] = group.group_name
+
+ returned_result.append(result)
+
+ return Response(returned_result)
diff --git a/seahub/api2/endpoints/shared_repos.py b/seahub/api2/endpoints/shared_repos.py
new file mode 100644
index 0000000000..50cb081a6d
--- /dev/null
+++ b/seahub/api2/endpoints/shared_repos.py
@@ -0,0 +1,324 @@
+import logging
+
+from rest_framework.authentication import SessionAuthentication
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from rest_framework import status
+
+import seaserv
+from seaserv import seafile_api, ccnet_api
+
+from seahub.api2.utils import api_error
+from seahub.api2.authentication import TokenAuthentication
+from seahub.api2.throttling import UserRateThrottle
+
+from seahub.utils import is_org_context, is_valid_username, send_perm_audit_msg
+from seahub.base.templatetags.seahub_tags import email2nickname
+
+logger = logging.getLogger(__name__)
+
+class SharedRepos(APIView):
+
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated,)
+ throttle_classes = (UserRateThrottle,)
+
+ def get(self, request, format=None):
+ """ List all shared out repos.
+
+ Permission checking:
+ 1. all authenticated user can perform this action.
+ """
+
+ shared_repos = []
+ username = request.user.username
+ try:
+ if is_org_context(request):
+ org_id = request.user.org.org_id
+ shared_repos += seafile_api.get_org_share_out_repo_list(org_id, username, -1, -1)
+ shared_repos += seaserv.seafserv_threaded_rpc.get_org_group_repos_by_owner(org_id, username)
+ shared_repos += seaserv.seafserv_threaded_rpc.list_org_inner_pub_repos_by_owner(org_id, username)
+ else:
+ shared_repos += seafile_api.get_share_out_repo_list(username, -1, -1)
+ shared_repos += seafile_api.get_group_repos_by_owner(username)
+ if not request.cloud_mode:
+ shared_repos += seaserv.list_inner_pub_repos_by_owner(username)
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ returned_result = []
+ shared_repos.sort(lambda x, y: cmp(x.repo_name, y.repo_name))
+ for repo in shared_repos:
+ if repo.is_virtual:
+ continue
+
+ result = {}
+ result['repo_id'] = repo.repo_id
+ result['repo_name'] = repo.repo_name
+ result['share_type'] = repo.share_type
+ result['share_permission'] = repo.permission
+
+ if repo.share_type == 'personal':
+ result['user_name'] = email2nickname(repo.user)
+ result['user_email'] = repo.user
+
+ if repo.share_type == 'group':
+ group = ccnet_api.get_group(repo.group_id)
+ result['group_id'] = repo.group_id
+ result['group_name'] = group.group_name
+
+ returned_result.append(result)
+
+ return Response(returned_result)
+
+
+class SharedRepo(APIView):
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated,)
+ throttle_classes = (UserRateThrottle,)
+
+ def put(self, request, repo_id, format=None):
+ """ Update permission of a shared repo.
+
+ Permission checking:
+ 1. Only repo owner can update.
+ """
+
+ # argument check
+ permission = request.data.get('permission', None)
+ if permission not in ['r', 'rw']:
+ error_msg = 'permission invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ share_type = request.data.get('share_type', None)
+ if not share_type:
+ error_msg = 'share_type invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ if share_type not in ('personal', 'group', 'public'):
+ error_msg = "share_type can only be 'personal' or 'group' or 'public'."
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ # recourse check
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ # permission check
+ username = request.user.username
+ if is_org_context(request):
+ repo_owner = seafile_api.get_org_repo_owner(repo_id)
+ else:
+ repo_owner = seafile_api.get_repo_owner(repo_id)
+
+ if username != repo_owner:
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ # update share permission
+ if share_type == 'personal':
+ shared_to = request.data.get('user', None)
+ if not shared_to or not is_valid_username(shared_to):
+ error_msg = 'user invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ try:
+ if is_org_context(request):
+ org_id = request.user.org.org_id
+ seaserv.seafserv_threaded_rpc.org_set_share_permission(
+ org_id, repo_id, username, shared_to, permission)
+ else:
+ seafile_api.set_share_permission(repo_id,
+ username, shared_to, permission)
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ send_perm_audit_msg('modify-repo-perm', username,
+ shared_to, repo_id, '/', permission)
+
+ if share_type == 'group':
+ group_id = request.data.get('group_id', None)
+ if not group_id:
+ error_msg = 'group_id invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ try:
+ group_id = int(group_id)
+ except ValueError:
+ error_msg = 'group_id must be integer.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ group = ccnet_api.get_group(group_id)
+ if not group:
+ error_msg = 'Group %s not found.' % group_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ try:
+ if is_org_context(request):
+ org_id = request.user.org.org_id
+ seaserv.seafserv_threaded_rpc.set_org_group_repo_permission(
+ org_id, group_id, repo_id, permission)
+ else:
+ seafile_api.set_group_repo_permission(
+ group_id, repo_id, permission)
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ send_perm_audit_msg('modify-repo-perm', username,
+ group_id, repo_id, '/', permission)
+
+ if share_type == 'public':
+ try:
+ if is_org_context(request):
+ org_id = request.user.org.org_id
+ seaserv.seafserv_threaded_rpc.set_org_inner_pub_repo(
+ org_id, repo_id, permission)
+ else:
+ seafile_api.add_inner_pub_repo(repo_id, permission)
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ send_perm_audit_msg('modify-repo-perm', username,
+ 'all', repo_id, '/', permission)
+
+ return Response({'success': True})
+
+ def delete(self, request, repo_id, format=None):
+ """ Unshare a repo.
+
+ Permission checking:
+ 1. Only repo owner can unshare a library.
+ """
+
+ # argument check
+ share_type = request.GET.get('share_type', None)
+ if not share_type:
+ error_msg = 'share_type invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ if share_type not in ('personal', 'group', 'public'):
+ error_msg = "share_type can only be 'personal' or 'group' or 'public'."
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ # resource check
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ return api_error(status.HTTP_404_NOT_FOUND, 'Library %s not found.' % repo_id)
+
+ # permission check
+ username = request.user.username
+ if is_org_context(request):
+ repo_owner = seafile_api.get_org_repo_owner(repo_id)
+ else:
+ repo_owner = seafile_api.get_repo_owner(repo_id)
+
+ if username != repo_owner:
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ # delete share
+ org_id = None
+ if is_org_context(request):
+ org_id = request.user.org.org_id
+
+ if share_type == 'personal':
+ user = request.GET.get('user', None)
+ if not user or not is_valid_username(user):
+ error_msg = 'user invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ # if user not found, permission will be None
+ permission = seafile_api.check_permission_by_path(
+ repo_id, '/', user)
+
+ try:
+ if org_id:
+ seafile_api.org_remove_share(org_id, repo_id,
+ username, user)
+ else:
+ seafile_api.remove_share(repo_id, username, user)
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ send_perm_audit_msg('delete-repo-perm', username, user,
+ repo_id, '/', permission)
+
+ if share_type == 'group':
+ group_id = request.GET.get('group_id', None)
+ if not group_id:
+ error_msg = 'group_id invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ try:
+ group_id = int(group_id)
+ except ValueError:
+ error_msg = 'group_id must be integer.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ # hacky way to get group repo permission
+ permission = ''
+ if org_id:
+ for e in seafile_api.list_org_repo_shared_group(
+ org_id, username, repo_id):
+ if e.group_id == group_id:
+ permission = e.perm
+ break
+ else:
+ for e in seafile_api.list_repo_shared_group_by_user(username, repo_id):
+ if e.group_id == group_id:
+ permission = e.perm
+ break
+
+ try:
+ if org_id:
+ seaserv.del_org_group_repo(repo_id, org_id, group_id)
+ else:
+ seafile_api.unset_group_repo(repo_id, group_id, username)
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ send_perm_audit_msg('delete-repo-perm', username, group_id,
+ repo_id, '/', permission)
+
+ if share_type == 'public':
+ pub_repos = []
+ if org_id:
+ pub_repos = seaserv.list_org_inner_pub_repos(org_id, username)
+
+ if not request.cloud_mode:
+ pub_repos = seaserv.list_inner_pub_repos(username)
+
+ try:
+ if org_id:
+ seaserv.seafserv_threaded_rpc.unset_org_inner_pub_repo(org_id, repo_id)
+ else:
+ seafile_api.remove_inner_pub_repo(repo_id)
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ permission = ''
+ for repo in pub_repos:
+ if repo.repo_id == repo_id:
+ permission = repo.permission
+ break
+
+ if permission:
+ send_perm_audit_msg('delete-repo-perm', username, 'all', repo_id, '/', permission)
+
+ return Response({'success': True})
diff --git a/seahub/api2/endpoints/upload_links.py b/seahub/api2/endpoints/upload_links.py
index 241b8ccc49..a2fb9e5a7d 100644
--- a/seahub/api2/endpoints/upload_links.py
+++ b/seahub/api2/endpoints/upload_links.py
@@ -1,3 +1,4 @@
+import os
import logging
from constance import config
@@ -19,6 +20,7 @@ from seahub.api2.throttling import UserRateThrottle
from seahub.share.models import UploadLinkShare
from seahub.utils import gen_shared_upload_link
from seahub.views import check_folder_permission
+from seahub.utils.timeutils import datetime_to_isoformat_timestr
logger = logging.getLogger(__name__)
@@ -36,9 +38,30 @@ class UploadLinks(APIView):
data = {}
token = uls.token
- data['repo_id'] = uls.repo_id
- data['path'] = uls.path
- data['ctime'] = uls.ctime
+ repo_id = uls.repo_id
+ try:
+ repo = seafile_api.get_repo(repo_id)
+ except Exception as e:
+ logger.error(e)
+ repo = None
+
+ path = uls.path
+ if path:
+ obj_name = '/' if path == '/' else os.path.basename(path.rstrip('/'))
+ else:
+ obj_name = ''
+
+ if uls.ctime:
+ ctime = datetime_to_isoformat_timestr(uls.ctime)
+ else:
+ ctime = ''
+
+ data['repo_id'] = repo_id
+ data['repo_name'] = repo.repo_name if repo else ''
+ data['path'] = path
+ data['obj_name'] = obj_name
+ data['view_cnt'] = uls.view_cnt
+ data['ctime'] = ctime
data['link'] = gen_shared_upload_link(token)
data['token'] = token
data['username'] = uls.username
@@ -46,13 +69,20 @@ class UploadLinks(APIView):
return data
def get(self, request):
- """ get upload link.
+ """ Get all upload links of a user.
+
+ Permission checking:
+ 1. default(NOT guest) user;
"""
if not self._can_generate_shared_link(request):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+ # get all upload links
+ username = request.user.username
+ upload_link_shares = UploadLinkShare.objects.filter(username=username)
+
repo_id = request.GET.get('repo_id', None)
if repo_id:
repo = seafile_api.get_repo(repo_id)
@@ -60,41 +90,27 @@ class UploadLinks(APIView):
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
- # repo level permission check
- if not check_folder_permission(request, repo_id, '/'):
- error_msg = 'Permission denied.'
- return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+ # filter share links by repo
+ upload_link_shares = filter(lambda ufs: ufs.repo_id==repo_id, upload_link_shares)
- path = request.GET.get('path', None)
- if path:
- try:
- dir_id = seafile_api.get_dir_id_by_path(repo_id, path)
- except SearpcError as e:
- logger.error(e)
- error_msg = 'Internal Server Error'
- return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+ path = request.GET.get('path', None)
+ if path:
+ try:
+ dir_id = seafile_api.get_dir_id_by_path(repo_id, path)
+ except SearpcError as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
- if not dir_id:
- error_msg = 'folder %s not found.' % path
- return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+ if not dir_id:
+ error_msg = 'folder %s not found.' % path
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
- # folder permission check
- if not check_folder_permission(request, repo_id, path):
- error_msg = 'Permission denied.'
- return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+ if path[-1] != '/':
+ path = path + '/'
- username = request.user.username
- upload_link_shares = UploadLinkShare.objects.filter(username=username)
-
- # filter result by args
- if repo_id:
- upload_link_shares = filter(lambda ufs: ufs.repo_id == repo_id, upload_link_shares)
-
- if path:
- if path[-1] != '/':
- path = path + '/'
-
- upload_link_shares = filter(lambda ufs: ufs.path == path, upload_link_shares)
+ # filter share links by path
+ upload_link_shares = filter(lambda ufs: ufs.path==path, upload_link_shares)
result = []
for uls in upload_link_shares:
@@ -102,33 +118,42 @@ class UploadLinks(APIView):
result.append(link_info)
if len(result) == 1:
- result = result[0]
+ result = result
+ else:
+ result.sort(lambda x, y: cmp(x['obj_name'], y['obj_name']))
return Response(result)
def post(self, request):
- """ create upload link.
+ """ Create upload link.
+
+ Permission checking:
+ 1. default(NOT guest) user;
+ 2. user with 'rw' permission;
"""
- if not self._can_generate_shared_link(request):
- error_msg = 'Permission denied.'
- return api_error(status.HTTP_403_FORBIDDEN, error_msg)
-
+ # argument check
repo_id = request.data.get('repo_id', None)
if not repo_id:
error_msg = 'repo_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
- repo = seafile_api.get_repo(repo_id)
- if not repo:
- error_msg = 'Library %s not found.' % repo_id
- return api_error(status.HTTP_404_NOT_FOUND, error_msg)
-
path = request.data.get('path', None)
if not path:
error_msg = 'path invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+ password = request.data.get('password', None)
+ if password and len(password) < config.SHARE_LINK_PASSWORD_MIN_LENGTH:
+ error_msg = _('Password is too short')
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ # resource check
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
try:
dir_id = seafile_api.get_dir_id_by_path(repo_id, path)
except SearpcError as e:
@@ -140,13 +165,12 @@ class UploadLinks(APIView):
error_msg = 'folder %s not found.' % path
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
- password = request.data.get('password', None)
- if password and len(password) < config.SHARE_LINK_PASSWORD_MIN_LENGTH:
- error_msg = _('Password is too short')
- return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+ # permission check
+ if not self._can_generate_shared_link(request):
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
- user_perm = check_folder_permission(request, repo_id, '/')
- if user_perm != 'rw':
+ if check_folder_permission(request, repo_id, path) != 'rw':
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
@@ -170,20 +194,10 @@ class UploadLink(APIView):
return request.user.permissions.can_generate_shared_link()
def get(self, request, token):
- """ get upload link info.
- """
+ """ Get upload link info.
- try:
- uls = UploadLinkShare.objects.get(token=token)
- except UploadLinkShare.DoesNotExist:
- error_msg = 'token %s not found.' % token
- return api_error(status.HTTP_404_NOT_FOUND, error_msg)
-
- link_info = self._get_upload_link_info(uls)
- return Response(link_info)
-
- def delete(self, request, token):
- """ delete upload link.
+ Permission checking:
+ 1. default(NOT guest) user;
"""
if not self._can_generate_shared_link(request):
@@ -196,6 +210,26 @@ class UploadLink(APIView):
error_msg = 'token %s not found.' % token
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+ link_info = self._get_upload_link_info(uls)
+ return Response(link_info)
+
+ def delete(self, request, token):
+ """ Delete upload link.
+
+ Permission checking:
+ 1. default(NOT guest) user;
+ 2. link owner;
+ """
+
+ if not self._can_generate_shared_link(request):
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ try:
+ uls = UploadLinkShare.objects.get(token=token)
+ except UploadLinkShare.DoesNotExist:
+ return Response({'success': True})
+
username = request.user.username
if not uls.is_owner(username):
error_msg = 'Permission denied.'
@@ -203,8 +237,9 @@ class UploadLink(APIView):
try:
uls.delete()
- return Response({'success': True})
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ return Response({'success': True})
diff --git a/seahub/api2/views.py b/seahub/api2/views.py
index ae9c563414..c1f51c1395 100644
--- a/seahub/api2/views.py
+++ b/seahub/api2/views.py
@@ -2959,7 +2959,6 @@ class SharedLinksView(APIView):
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
- # from seahub.share.view::list_shared_links
def get(self, request, format=None):
username = request.user.username
diff --git a/seahub/share/templates/share/links.html b/seahub/share/templates/share/links.html
deleted file mode 100644
index 69a88308aa..0000000000
--- a/seahub/share/templates/share/links.html
+++ /dev/null
@@ -1,259 +0,0 @@
-{% extends 'home_base.html' %}
-{% load seahub_tags i18n %}
-{% load url from future %}
-
-{% block sub_title %}{% trans "Links - Share" %} - {% endblock %}
-
-{% block cur_share_links %}tab-cur{% endblock %}
-
-{% block right_panel %}
-
-
-
- {% if fileshares %}
-
-
-
- {% trans "Name"%}
- {% trans "Library"%}
- {% trans "Visits"%}
- {% trans "Expiration"%}
-
-
- {% for fs in fileshares %}
-
- {% if fs.s_type == 'f' %}
-
- {{ fs.filename }}
- {% else %}
-
- {{ fs.filename }}
- {% endif %}
- {{ fs.repo.name }}
- {{ fs.view_cnt }}
- {% if fs.expire_date %}
- {% if fs.is_expired %}
- {{ fs.expire_date|date:'Y-m-d' }}
- {% else %}
- {{ fs.expire_date|date:'Y-m-d' }}
- {% endif %}
- {% else %}
- --
- {% endif %}
-
-
-
-
-
- {% endfor %}
-
- {% else %}
-
-
{% trans "You don't have any download link"%}
-
{% trans "You can generate a download link for a folder or a file. People receive this link can view the folder or the file online." %}
-
- {% endif %}
-
-
-
- {% if uploadlinks %}
-
-
-
- {% trans "Name"%}
- {% trans "Library"%}
- {% trans "Visits"%}
- {% trans "Operations"%}
-
- {% for link in uploadlinks %}
-
-
- {{ link.dir_name }}
- {{ link.repo.name }}
- {{ link.view_cnt }}
-
-
-
-
-
- {% endfor %}
-
- {% else %}
-
-
{% trans "You don't have any upload link"%}
-
{% trans "You can generate an upload link from any folder. People receive this link can upload files to this folder." %}
-
- {% endif %}
-
-
-
-{% endblock %}
-
-{% block extra_script %}{{block.super}}
-{% if fileshares %}
-
-{% endif %}
-
-{% endblock %}
diff --git a/seahub/share/templates/share/list_priv_shared_folders.html b/seahub/share/templates/share/list_priv_shared_folders.html
deleted file mode 100644
index 648361e18c..0000000000
--- a/seahub/share/templates/share/list_priv_shared_folders.html
+++ /dev/null
@@ -1,117 +0,0 @@
-{% extends 'home_base.html' %}
-{% load seahub_tags i18n %}
-{% load url from future %}
-
-{% block sub_title %}{% trans "Folders - Share" %} - {% endblock %}
-
-{% block cur_share_folders %}tab-cur{% endblock %}
-
-{% block right_panel %}
-{% trans "Folders" %}
-{% if shared_folders %}
-
-
-
- {% trans "Name"%}
- {% trans "Share To"%}
- {% trans "Permission"%}
-
-
- {% for repo in shared_folders %}
-
-
- {{ repo.props.repo_name }}
- {{ repo.props.user | email2nickname }}
-
-
-
{{ repo.share_permission }}
-
-
-
- {{ repo.share_permission }}
- {% if repo.props.permission == 'rw' %}
- {% trans "Read-Only"%}
- {% else %}
- {% trans "Read-Write"%}
- {% endif %}
-
-
-
- {% if repo.props.share_type == 'group' %}
-
- {% endif %}
- {% if repo.props.share_type == 'personal' %}
-
- {% endif %}
- {% if repo.props.share_type == 'public' %}
-
- {% endif %}
-
-
- {% endfor %}
-
-{% else %}
-
-
{% trans "You have not shared any folder" %}
-
{% trans "You can share a single folder with a registered user if you don't want to share a whole library." %}
-
-{% endif %}
-{% endblock %}
-
-{% block extra_script %}{{block.super}}
-
-{% endblock %}
diff --git a/seahub/share/templates/share/repos.html b/seahub/share/templates/share/repos.html
deleted file mode 100644
index a307ac4ea0..0000000000
--- a/seahub/share/templates/share/repos.html
+++ /dev/null
@@ -1,126 +0,0 @@
-{% extends 'home_base.html' %}
-{% load seahub_tags i18n %}
-{% load url from future %}
-
-{% block sub_title %}{% trans "Libraries - Share" %} - {% endblock %}
-
-{% block cur_share_libs %}tab-cur{% endblock %}
-
-{% block right_panel %}
-{% trans "Libraries" %}
-{% if out_repos %}
-
-
-
- {% trans "Name"%}
- {% trans "Share To"%}
- {% trans "Permission"%}
-
-
- {% for repo in out_repos %}
-
- {% if repo.encrypted %}
-
- {% else %}
-
- {% endif %}
- {{ repo.props.repo_name }}
- {{ repo.props.user | email2nickname }}
-
-
-
{{ repo.share_permission }}
-
-
-
- {{ repo.share_permission }}
- {% if repo.props.permission == 'rw' %}
- {% trans "Read-Only"%}
- {% else %}
- {% trans "Read-Write"%}
- {% endif %}
-
-
-
- {% if repo.props.share_type == 'group' %}
-
- {% endif %}
- {% if repo.props.share_type == 'personal' %}
-
- {% endif %}
- {% if repo.props.share_type == 'public' %}
-
- {% endif %}
-
-
- {% endfor %}
-
-{% else %}
-
-
{% trans "You have not shared any library" %}
-
{% trans "You can share libraries to your friends and colleagues by clicking the share icon of your own libraries in your home page or creating a new library in groups you are in." %}
-
-{% endif %}
-{% endblock %}
-
-{% block extra_script %}{{block.super}}
-
-{% endblock %}
diff --git a/seahub/share/urls.py b/seahub/share/urls.py
index 89c33bb7c7..d808f13a90 100644
--- a/seahub/share/urls.py
+++ b/seahub/share/urls.py
@@ -3,19 +3,9 @@ from django.conf.urls import patterns, url
from views import *
urlpatterns = patterns('',
- url(r'^$', list_shared_repos, name='share_admin'),
- url(r'^links/$', list_shared_links, name='list_shared_links'),
- url(r'^folders/$', list_priv_shared_folders, name='list_priv_shared_folders'),
- url(r'^add/$', share_repo, name='share_repo'),
- url(r'^remove/$', repo_remove_share, name='repo_remove_share'),
- url(r'^ajax/link/remove/$', ajax_remove_shared_link, name='ajax_remove_shared_link'),
url(r'^link/send/$', send_shared_link, name='send_shared_link'),
url(r'^link/save/$', save_shared_link, name='save_shared_link'),
- url(r'^ajax/upload_link/remove/$', ajax_remove_shared_upload_link, name='ajax_remove_shared_upload_link'),
url(r'^upload_link/send/$', send_shared_upload_link, name='send_shared_upload_link'),
- url(r'^permission_admin/$', share_permission_admin, name='share_permission_admin'),
- url(r'^ajax/get-download-link/$', ajax_get_download_link, name='ajax_get_download_link'),
- url(r'^ajax/get-upload-link/$', ajax_get_upload_link, name='ajax_get_upload_link'),
url(r'^ajax/private-share-dir/$', ajax_private_share_dir, name='ajax_private_share_dir'),
url(r'^ajax/get-link-audit-code/$', ajax_get_link_audit_code, name='ajax_get_link_audit_code'),
)
diff --git a/seahub/share/views.py b/seahub/share/views.py
index 0bc290ce26..218d782884 100644
--- a/seahub/share/views.py
+++ b/seahub/share/views.py
@@ -6,35 +6,29 @@ from dateutil.relativedelta import relativedelta
from constance import config
from django.core.cache import cache
-from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect, Http404, \
HttpResponseBadRequest
-from django.shortcuts import render_to_response
-from django.template import RequestContext
from django.utils.translation import ugettext as _
from django.contrib import messages
from django.utils import timezone
from django.utils.html import escape
-# from django.contrib.sites.models import RequestSite
import seaserv
from seaserv import seafile_api
-from seaserv import ccnet_threaded_rpc, is_org_group, \
- get_org_id_by_group, del_org_group_repo, unset_inner_pub_repo
+from seaserv import ccnet_threaded_rpc
from pysearpc import SearpcError
-from seahub.share.forms import RepoShareForm, FileLinkShareForm, \
+from seahub.share.forms import FileLinkShareForm, \
UploadLinkShareForm
from seahub.share.models import FileShare, UploadLinkShare, OrgFileShare
from seahub.share.signals import share_repo_to_user_successful
from seahub.auth.decorators import login_required, login_required_ajax
-from seahub.base.decorators import user_mods_check, require_POST
+from seahub.base.decorators import require_POST
from seahub.contacts.signals import mail_sended
from seahub.views import is_registered_user, check_folder_permission
-from seahub.utils import render_permission_error, string2list, render_error, \
- gen_shared_link, gen_shared_upload_link, gen_dir_share_link, \
- gen_file_share_link, IS_EMAIL_CONFIGURED, check_filename_with_rename, \
+from seahub.utils import string2list, gen_shared_link, \
+ gen_shared_upload_link, IS_EMAIL_CONFIGURED, check_filename_with_rename, \
is_valid_username, is_valid_email, send_html_email, is_org_context, \
- send_perm_audit_msg, get_origin_repo_info, gen_token, normalize_cache_key
+ gen_token, normalize_cache_key
from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
from seahub.settings import SITE_ROOT, REPLACE_FROM_EMAIL, ADD_REPLY_TO_HEADER
from seahub.profile.models import Profile
@@ -47,14 +41,6 @@ def is_org_repo_owner(username, repo_id):
owner = seaserv.seafserv_threaded_rpc.get_org_repo_owner(repo_id)
return True if owner == username else False
-def get_org_group_repos_by_owner(org_id, username):
- return seaserv.seafserv_threaded_rpc.get_org_group_repos_by_owner(org_id,
- username)
-
-def list_org_inner_pub_repos_by_owner(org_id, username):
- return seaserv.seafserv_threaded_rpc.list_org_inner_pub_repos_by_owner(
- org_id, username)
-
def org_share_repo(org_id, repo_id, from_user, to_user, permission):
return seaserv.seafserv_threaded_rpc.org_add_share(org_id, repo_id,
from_user, to_user,
@@ -65,27 +51,6 @@ def org_remove_share(org_id, repo_id, from_user, to_user):
from_user, to_user)
########## functions
-
-def share_to_public(request, repo, permission):
- """Share repo to public with given permission.
- """
- try:
- if is_org_context(request):
- org_id = request.user.org.org_id
- seaserv.seafserv_threaded_rpc.set_org_inner_pub_repo(
- org_id, repo.id, permission)
- elif request.cloud_mode:
- return # no share to public in cloud mode
- else:
- seafile_api.add_inner_pub_repo(repo.id, permission)
- except Exception, e:
- logger.error(e)
- messages.error(request, _(u'Failed to share to all members, please try again later.'))
- else:
- msg = _(u'Shared to all members successfully, go check it at Shares .') % \
- (reverse('share_admin'))
- messages.success(request, msg, extra_tags='safe')
-
def share_to_group(request, repo, group, permission):
"""Share repo to group with given permission.
"""
@@ -149,495 +114,7 @@ def share_to_user(request, repo, to_user, permission):
to_user=to_user, repo=repo)
return True
-
-########## views
-@login_required
-@require_POST
-def share_repo(request):
- """
- Handle POST method to share a repo to public/groups/users based on form
- data. Return to ``myhome`` page and notify user whether success or failure.
- """
- next = request.META.get('HTTP_REFERER', None)
- if not next:
- next = SITE_ROOT
-
- form = RepoShareForm(request.POST)
- if not form.is_valid():
- # TODO: may display error msg on form
- raise Http404
-
- email_or_group = form.cleaned_data['email_or_group']
- repo_id = form.cleaned_data['repo_id']
- permission = form.cleaned_data['permission']
-
- repo = seafile_api.get_repo(repo_id)
- if not repo:
- raise Http404
-
- # Test whether user is the repo owner.
- username = request.user.username
- if not seafile_api.is_repo_owner(username, repo_id) and \
- not is_org_repo_owner(username, repo_id):
- msg = _(u'Only the owner of the library has permission to share it.')
- messages.error(request, msg)
- return HttpResponseRedirect(next)
-
- # Parsing input values.
- share_to_all, share_to_groups, share_to_users = False, [], []
- user_groups = request.user.joined_groups
- share_to_list = string2list(email_or_group)
- for share_to in share_to_list:
- if share_to == 'all':
- share_to_all = True
- elif share_to.find('@') == -1:
- for user_group in user_groups:
- if user_group.group_name == share_to:
- share_to_groups.append(user_group)
- else:
- share_to = share_to.lower()
- if is_valid_username(share_to):
- share_to_users.append(share_to)
-
- origin_repo_id, origin_path = get_origin_repo_info(repo.id)
- if origin_repo_id is not None:
- perm_repo_id = origin_repo_id
- perm_path = origin_path
- else:
- perm_repo_id = repo.id
- perm_path = '/'
-
- if share_to_all:
- share_to_public(request, repo, permission)
- send_perm_audit_msg('add-repo-perm', username, 'all', \
- perm_repo_id, perm_path, permission)
-
- for group in share_to_groups:
- if share_to_group(request, repo, group, permission):
- send_perm_audit_msg('add-repo-perm', username, group.id, \
- perm_repo_id, perm_path, permission)
-
- for email in share_to_users:
- # Add email to contacts.
- mail_sended.send(sender=None, user=request.user.username, email=email)
- if share_to_user(request, repo, email, permission):
- send_perm_audit_msg('add-repo-perm', username, email, \
- perm_repo_id, perm_path, permission)
-
- return HttpResponseRedirect(next)
-
-@login_required
-@require_POST
-def repo_remove_share(request):
- """
- If repo is shared from one person to another person, only these two person
- can remove share.
- If repo is shared from one person to a group, then only the one share the
- repo and group staff can remove share.
- """
- repo_id = request.GET.get('repo_id', '')
- group_id = request.GET.get('gid', '')
- from_email = request.GET.get('from', '')
- perm = request.GET.get('permission', None)
- if not is_valid_username(from_email) or perm is None:
- return render_error(request, _(u'Argument is not valid'))
- username = request.user.username
-
- repo = seafile_api.get_repo(repo_id)
- if not repo:
- return render_error(request, _(u'Library does not exist'))
-
- origin_repo_id, origin_path = get_origin_repo_info(repo.id)
- if origin_repo_id is not None:
- perm_repo_id = origin_repo_id
- perm_path = origin_path
- else:
- perm_repo_id = repo.id
- perm_path = '/'
-
- # if request params don't have 'gid', then remove repos that share to
- # to other person; else, remove repos that share to groups
- if not group_id:
- to_email = request.GET.get('to', '')
- if not is_valid_username(to_email):
- return render_error(request, _(u'Argument is not valid'))
-
- if username != from_email and username != to_email:
- return render_permission_error(request, _(u'Failed to remove share'))
-
- if is_org_context(request):
- org_id = request.user.org.org_id
- org_remove_share(org_id, repo_id, from_email, to_email)
- else:
- seaserv.remove_share(repo_id, from_email, to_email)
- send_perm_audit_msg('delete-repo-perm', from_email, to_email, \
- perm_repo_id, perm_path, perm)
- else:
- try:
- group_id = int(group_id)
- except:
- return render_error(request, _(u'group id is not valid'))
-
- group = seaserv.get_group(group_id)
- if not group:
- return render_error(request, _(u"Failed to unshare: the group doesn't exist."))
-
- if not seaserv.check_group_staff(group_id, username) \
- and username != from_email:
- return render_permission_error(request, _(u'Failed to remove share'))
-
- if is_org_group(group_id):
- org_id = get_org_id_by_group(group_id)
- del_org_group_repo(repo_id, org_id, group_id)
- else:
- seafile_api.unset_group_repo(repo_id, group_id, from_email)
- send_perm_audit_msg('delete-repo-perm', from_email, group_id, \
- perm_repo_id, perm_path, perm)
-
- messages.success(request, _('Successfully removed share'))
-
- next = request.META.get('HTTP_REFERER', SITE_ROOT)
- return HttpResponseRedirect(next)
-
-def get_share_out_repo_list(request):
- """List repos that @user share to other users.
-
- Returns:
- A list of repos.
- """
- username = request.user.username
- if is_org_context(request):
- org_id = request.user.org.org_id
- return seafile_api.get_org_share_out_repo_list(org_id, username,
- -1, -1)
- else:
- return seafile_api.get_share_out_repo_list(username, -1, -1)
-
-def get_group_repos_by_owner(request):
- """List repos that @user share to groups.
-
- Returns:
- A list of repos.
- """
- username = request.user.username
- if is_org_context(request):
- org_id = request.user.org.org_id
- return get_org_group_repos_by_owner(org_id, username)
- else:
- return seaserv.get_group_repos_by_owner(username)
-
-def list_inner_pub_repos_by_owner(request):
- """List repos that @user share to organizatoin.
-
- Returns:
- A list of repos, or empty list if in cloud_mode.
- """
- username = request.user.username
- if is_org_context(request):
- org_id = request.user.org.org_id
- return list_org_inner_pub_repos_by_owner(org_id, username)
- elif request.cloud_mode:
- return []
- else:
- return seaserv.list_inner_pub_repos_by_owner(username)
-
-def list_share_out_repos(request):
- shared_repos = []
-
- # repos shared from this user
- shared_repos += get_share_out_repo_list(request)
-
- # repos shared to groups
- group_repos = get_group_repos_by_owner(request)
- for repo in group_repos:
- group = ccnet_threaded_rpc.get_group(int(repo.group_id))
- if not group:
- repo.props.user = ''
- continue
- repo.props.user = group.props.group_name
- repo.props.user_info = repo.group_id
- shared_repos += group_repos
-
- # inner pub repos
- pub_repos = list_inner_pub_repos_by_owner(request)
- for repo in pub_repos:
- repo.props.user = _(u'all members')
- repo.props.user_info = 'all'
- shared_repos += pub_repos
-
- return shared_repos
-
-@login_required
-@user_mods_check
-def list_shared_repos(request):
- """ List user repos shared to users/groups/public.
- """
- share_out_repos = list_share_out_repos(request)
-
- out_repos = []
- for repo in share_out_repos:
- if repo.is_virtual: # skip virtual repos
- continue
-
- if repo.props.permission == 'rw':
- repo.share_permission = _(u'Read-Write')
- elif repo.props.permission == 'r':
- repo.share_permission = _(u'Read-Only')
- else:
- repo.share_permission = ''
-
- if repo.props.share_type == 'personal':
- repo.props.user_info = repo.props.user
- out_repos.append(repo)
-
- out_repos.sort(lambda x, y: cmp(x.repo_name, y.repo_name))
-
- return render_to_response('share/repos.html', {
- "out_repos": out_repos,
- }, context_instance=RequestContext(request))
-
-@login_required
-@user_mods_check
-def list_shared_links(request):
- """List shared links, and remove invalid links(file/dir is deleted or moved).
- """
- username = request.user.username
-
- # download links
- fileshares = FileShare.objects.filter(username=username)
- fs_files, fs_dirs = [], []
- for fs in fileshares:
- r = seafile_api.get_repo(fs.repo_id)
- if not r:
- fs.delete()
- continue
-
- if fs.is_file_share_link():
- if seafile_api.get_file_id_by_path(r.id, fs.path) is None:
- fs.delete()
- continue
- fs.filename = os.path.basename(fs.path)
- fs.shared_link = gen_file_share_link(fs.token)
- else:
- if seafile_api.get_dir_id_by_path(r.id, fs.path) is None:
- fs.delete()
- continue
- if fs.path != '/':
- fs.filename = os.path.basename(fs.path.rstrip('/'))
- else:
- fs.filename = fs.path
- fs.shared_link = gen_dir_share_link(fs.token)
- fs.repo = r
-
- if fs.expire_date is not None and timezone.now() > fs.expire_date:
- fs.is_expired = True
-
- fs_files.append(fs) if fs.is_file_share_link() else fs_dirs.append(fs)
- fs_files.sort(lambda x, y: cmp(x.filename, y.filename))
- fs_dirs.sort(lambda x, y: cmp(x.filename, y.filename))
-
- # upload links
- uploadlinks = UploadLinkShare.objects.filter(username=username)
- p_uploadlinks = []
- for link in uploadlinks:
- r = seafile_api.get_repo(link.repo_id)
- if not r:
- link.delete()
- continue
- if seafile_api.get_dir_id_by_path(r.id, link.path) is None:
- link.delete()
- continue
- if link.path != '/':
- link.dir_name = os.path.basename(link.path.rstrip('/'))
- else:
- link.dir_name = link.path
- link.shared_link = gen_shared_upload_link(link.token)
- link.repo = r
- p_uploadlinks.append(link)
- p_uploadlinks.sort(lambda x, y: cmp(x.dir_name, y.dir_name))
-
- return render_to_response('share/links.html', {
- "fileshares": fs_dirs + fs_files,
- "uploadlinks": p_uploadlinks,
- }, context_instance=RequestContext(request))
-
-@login_required
-@user_mods_check
-def list_priv_shared_folders(request):
- """List private shared folders.
-
- Arguments:
- - `request`:
- """
- share_out_repos = list_share_out_repos(request)
-
- shared_folders = []
- for repo in share_out_repos:
- if not repo.is_virtual: # skip non-virtual repos
- continue
-
- if repo.props.permission == 'rw':
- repo.share_permission = _(u'Read-Write')
- elif repo.props.permission == 'r':
- repo.share_permission = _(u'Read-Only')
- else:
- repo.share_permission = ''
-
- if repo.props.share_type == 'personal':
- repo.props.user_info = repo.props.user
- shared_folders.append(repo)
-
- shared_folders.sort(lambda x, y: cmp(x.repo_id, y.repo_id))
-
- return render_to_response('share/list_priv_shared_folders.html', {
- 'shared_folders': shared_folders,
- }, context_instance=RequestContext(request))
-
-@login_required_ajax
-def share_permission_admin(request):
- """Change repo share permission in ShareAdmin.
- """
- share_type = request.GET.get('share_type', '')
- content_type = 'application/json; charset=utf-8'
-
- form = RepoShareForm(request.POST)
- form.is_valid()
-
- email_or_group = form.cleaned_data['email_or_group']
- repo_id = form.cleaned_data['repo_id']
- permission = form.cleaned_data['permission']
- from_email = request.user.username
-
- repo = seafile_api.get_repo(repo_id)
- if not repo:
- return render_error(request, _(u'Library does not exist'))
-
- origin_repo_id, origin_path = get_origin_repo_info(repo.id)
- if origin_repo_id is not None:
- perm_repo_id = origin_repo_id
- perm_path = origin_path
- else:
- perm_repo_id = repo.id
- perm_path = '/'
-
-
- if share_type == 'personal':
- if not is_valid_username(email_or_group):
- return HttpResponse(json.dumps({'success': False}), status=400,
- content_type=content_type)
-
- try:
- if is_org_context(request):
- org_id = request.user.org.org_id
- seaserv.seafserv_threaded_rpc.org_set_share_permission(
- org_id, repo_id, from_email, email_or_group, permission)
- else:
- seafile_api.set_share_permission(repo_id, from_email,
- email_or_group, permission)
- send_perm_audit_msg('modify-repo-perm', from_email, \
- email_or_group, perm_repo_id, perm_path, permission)
-
- except SearpcError:
- return HttpResponse(json.dumps({'success': False}), status=500,
- content_type=content_type)
- return HttpResponse(json.dumps({'success': True}),
- content_type=content_type)
-
- elif share_type == 'group':
- try:
- if is_org_context(request):
- org_id = request.user.org.org_id
- seaserv.seafserv_threaded_rpc.set_org_group_repo_permission(
- org_id, int(email_or_group), repo_id, permission)
- else:
- group_id = int(email_or_group)
- seafile_api.set_group_repo_permission(group_id,
- repo_id,
- permission)
- send_perm_audit_msg('modify-repo-perm', from_email, \
- group_id, perm_repo_id, perm_path, permission)
- except SearpcError:
- return HttpResponse(json.dumps({'success': False}), status=500,
- content_type=content_type)
- return HttpResponse(json.dumps({'success': True}),
- content_type=content_type)
-
- elif share_type == 'public':
- try:
- if is_org_context(request):
- org_id = request.user.org.org_id
- seaserv.seafserv_threaded_rpc.set_org_inner_pub_repo(
- org_id, repo_id, permission)
- else:
- seafile_api.add_inner_pub_repo(repo_id, permission)
- send_perm_audit_msg('modify-repo-perm', from_email, 'all', \
- perm_repo_id, perm_path, permission)
- except SearpcError:
- return HttpResponse(json.dumps({'success': False}), status=500,
- content_type=content_type)
- return HttpResponse(json.dumps({'success': True}),
- content_type=content_type)
-
- else:
- return HttpResponse(json.dumps({'success': False}), status=400,
- content_type=content_type)
-
########## share link
-@login_required_ajax
-@require_POST
-def ajax_remove_shared_link(request):
- username = request.user.username
- content_type = 'application/json; charset=utf-8'
- result = {}
-
- token = request.POST.get('t')
- if not token:
- result = {'error': _(u"Argument missing")}
- return HttpResponse(json.dumps(result), status=400, content_type=content_type)
-
- try:
- link = FileShare.objects.get(token=token)
- except FileShare.DoesNotExist:
- result = {'error': _(u"The link doesn't exist")}
- return HttpResponse(json.dumps(result), status=400, content_type=content_type)
-
- if not link.is_owner(username):
- result = {'error': _("Permission denied")}
- return HttpResponse(json.dumps(result), status=403,
- content_type=content_type)
-
- link.delete()
- result = {'success': True}
- return HttpResponse(json.dumps(result), content_type=content_type)
-
-
-@login_required_ajax
-@require_POST
-def ajax_remove_shared_upload_link(request):
- username = request.user.username
- content_type = 'application/json; charset=utf-8'
- result = {}
-
- token = request.POST.get('t')
- if not token:
- result = {'error': _(u"Argument missing")}
- return HttpResponse(json.dumps(result), status=400, content_type=content_type)
-
- try:
- upload_link = UploadLinkShare.objects.get(token=token)
- except UploadLinkShare.DoesNotExist:
- result = {'error': _(u"The link doesn't exist")}
- return HttpResponse(json.dumps(result), status=400, content_type=content_type)
-
- if not upload_link.is_owner(username):
- result = {'error': _("Permission denied")}
- return HttpResponse(json.dumps(result), status=403,
- content_type=content_type)
- upload_link.delete()
- result = {'success': True}
- return HttpResponse(json.dumps(result), content_type=content_type)
-
-
@login_required_ajax
def send_shared_link(request):
"""
@@ -835,282 +312,6 @@ def send_shared_upload_link(request):
else:
return HttpResponseBadRequest(json.dumps(form.errors),
content_type=content_type)
-@login_required_ajax
-def ajax_get_upload_link(request):
- content_type = 'application/json; charset=utf-8'
-
- if request.method == 'GET':
- repo_id = request.GET.get('repo_id', None)
- path = request.GET.get('p', None)
-
- # augument check
- if not repo_id:
- data = json.dumps({'error': 'repo_id invalid.'})
- return HttpResponse(data, status=400, content_type=content_type)
-
- if not path:
- data = json.dumps({'error': 'p invalid.'})
- return HttpResponse(data, status=400, content_type=content_type)
-
- # resource check
- try:
- repo = seafile_api.get_repo(repo_id)
- except Exception as e:
- logger.error(e)
- data = json.dumps({'error': 'Internal Server Error'})
- return HttpResponse(data, status=500, content_type=content_type)
-
- if not repo:
- data = json.dumps({'error': 'Library %s not found.' % repo_id})
- return HttpResponse(data, status=404, content_type=content_type)
-
- if not path.endswith('/'):
- path = path + '/'
-
- if not seafile_api.get_dir_id_by_path(repo_id, path):
- data = json.dumps({'error': 'Folder %s not found.' % path})
- return HttpResponse(data, status=404, content_type=content_type)
-
- # permission check
- if not check_folder_permission(request, repo_id, path):
- data = json.dumps({'error': 'Permission denied.'})
- return HttpResponse(data, status=403, content_type=content_type)
-
- # get upload link
- username = request.user.username
- l = UploadLinkShare.objects.filter(repo_id=repo_id).filter(
- username=username).filter(path=path)
-
- data = {}
- if len(l) > 0:
- token = l[0].token
- data['upload_link'] = gen_shared_upload_link(token)
- data['token'] = token
-
- return HttpResponse(json.dumps(data), content_type=content_type)
-
- elif request.method == 'POST':
- repo_id = request.POST.get('repo_id', None)
- path = request.POST.get('p', None)
- use_passwd = True if int(request.POST.get('use_passwd', '0')) == 1 else False
- passwd = request.POST.get('passwd') if use_passwd else None
-
- # augument check
- if not repo_id:
- data = json.dumps({'error': 'repo_id invalid.'})
- return HttpResponse(data, status=400, content_type=content_type)
-
- if not path:
- data = json.dumps({'error': 'p invalid.'})
- return HttpResponse(data, status=400, content_type=content_type)
-
- if passwd and len(passwd) < config.SHARE_LINK_PASSWORD_MIN_LENGTH:
- data = json.dumps({'error': _('Password is too short')})
- return HttpResponse(data, status=400, content_type=content_type)
-
- # resource check
- try:
- repo = seafile_api.get_repo(repo_id)
- except Exception as e:
- logger.error(e)
- data = json.dumps({'error': 'Internal Server Error'})
- return HttpResponse(data, status=500, content_type=content_type)
-
- if not repo:
- data = json.dumps({'error': 'Library %s not found.' % repo_id})
- return HttpResponse(data, status=404, content_type=content_type)
-
- if not path.endswith('/'):
- path = path + '/'
-
- if not seafile_api.get_dir_id_by_path(repo_id, path):
- data = json.dumps({'error': 'Folder %s not found.' % path})
- return HttpResponse(data, status=404, content_type=content_type)
-
- # permission check
- # normal permission check & default/guest user permission check
- if check_folder_permission(request, repo_id, path) != 'rw' or \
- not request.user.permissions.can_generate_shared_link():
- data = json.dumps({'error': 'Permission denied.'})
- return HttpResponse(data, status=403, content_type=content_type)
-
- # generate upload link
- l = UploadLinkShare.objects.filter(repo_id=repo_id).filter(
- username=request.user.username).filter(path=path)
-
- if len(l) > 0:
- # if already exist
- upload_link = l[0]
- token = upload_link.token
- else:
- # generate new
- username = request.user.username
- uls = UploadLinkShare.objects.create_upload_link_share(
- username, repo_id, path, passwd)
- token = uls.token
-
- shared_upload_link = gen_shared_upload_link(token)
- data = json.dumps({'token': token, 'upload_link': shared_upload_link})
-
- return HttpResponse(data, content_type=content_type)
-
-@login_required_ajax
-def ajax_get_download_link(request):
- """
- Handle ajax request to generate file or dir shared link.
- """
- content_type = 'application/json; charset=utf-8'
-
- if request.method == 'GET':
- repo_id = request.GET.get('repo_id', None)
- share_type = request.GET.get('type', 'f')
- path = request.GET.get('p', None)
-
- # augument check
- if not repo_id:
- data = json.dumps({'error': 'repo_id invalid.'})
- return HttpResponse(data, status=400, content_type=content_type)
-
- if not path:
- data = json.dumps({'error': 'p invalid.'})
- return HttpResponse(data, status=400, content_type=content_type)
-
- if share_type not in ('f', 'd'):
- data = json.dumps({'error': 'type invalid.'})
- return HttpResponse(data, status=400, content_type=content_type)
-
- # resource check
- try:
- repo = seafile_api.get_repo(repo_id)
- except Exception as e:
- logger.error(e)
- data = json.dumps({'error': 'Internal Server Error'})
- return HttpResponse(data, status=500, content_type=content_type)
-
- if not repo:
- data = json.dumps({'error': 'Library %s not found.' % repo_id})
- return HttpResponse(data, status=404, content_type=content_type)
-
- if share_type == 'f':
- if not seafile_api.get_file_id_by_path(repo_id, path):
- data = json.dumps({'error': 'File %s not found.' % path})
- return HttpResponse(data, status=404, content_type=content_type)
-
- if share_type == 'd':
- if not path.endswith('/'):
- path = path + '/'
-
- if not seafile_api.get_dir_id_by_path(repo_id, path):
- data = json.dumps({'error': 'Folder %s not found.' % path})
- return HttpResponse(data, status=404, content_type=content_type)
-
- # permission check
- if not check_folder_permission(request, repo_id, path):
- data = json.dumps({'error': 'Permission denied.'})
- return HttpResponse(data, status=403, content_type=content_type)
-
- # get download link
- username = request.user.username
- l = FileShare.objects.filter(repo_id=repo_id).filter(
- username=username).filter(path=path)
-
- data = {}
- if len(l) > 0:
- token = l[0].token
- data['download_link'] = gen_shared_link(token, l[0].s_type)
- data['token'] = token
- data['is_expired'] = l[0].is_expired()
-
- return HttpResponse(json.dumps(data), content_type=content_type)
-
- elif request.method == 'POST':
- repo_id = request.POST.get('repo_id', None)
- path = request.POST.get('p', None)
- share_type = request.POST.get('type', 'f')
- use_passwd = True if int(request.POST.get('use_passwd', '0')) == 1 else False
- passwd = request.POST.get('passwd') if use_passwd else None
-
- # augument check
- if not repo_id:
- data = json.dumps({'error': 'repo_id invalid.'})
- return HttpResponse(data, status=400, content_type=content_type)
-
- if not path:
- data = json.dumps({'error': 'p invalid.'})
- return HttpResponse(data, status=400, content_type=content_type)
-
- if share_type not in ('f', 'd'):
- data = json.dumps({'error': 'type invalid.'})
- return HttpResponse(data, status=400, content_type=content_type)
-
- if passwd and len(passwd) < config.SHARE_LINK_PASSWORD_MIN_LENGTH:
- data = json.dumps({'error': _('Password is too short')})
- return HttpResponse(data, status=400, content_type=content_type)
-
- try:
- expire_days = int(request.POST.get('expire_days', 0))
- except ValueError:
- expire_days = 0
-
- if expire_days <= 0:
- expire_date = None
- else:
- expire_date = timezone.now() + relativedelta(days=expire_days)
-
- # resource check
- try:
- repo = seafile_api.get_repo(repo_id)
- except Exception as e:
- logger.error(e)
- data = json.dumps({'error': 'Internal Server Error'})
- return HttpResponse(data, status=500, content_type=content_type)
-
- if not repo:
- data = json.dumps({'error': 'Library %s not found.' % repo_id})
- return HttpResponse(data, status=404, content_type=content_type)
-
- if share_type == 'f':
- if not seafile_api.get_file_id_by_path(repo_id, path):
- data = json.dumps({'error': 'File %s not found.' % path})
- return HttpResponse(data, status=404, content_type=content_type)
-
- if share_type == 'd':
- if not path.endswith('/'):
- path = path + '/'
-
- if not seafile_api.get_dir_id_by_path(repo_id, path):
- data = json.dumps({'error': 'Folder %s not found.' % path})
- return HttpResponse(data, status=404, content_type=content_type)
-
- # permission check
- # normal permission check & default/guest user permission check
- if check_folder_permission(request, repo_id, path) != 'rw' or \
- not request.user.permissions.can_generate_shared_link():
- data = json.dumps({'error': 'Permission denied.'})
- return HttpResponse(data, status=403, content_type=content_type)
-
- username = request.user.username
- if share_type == 'f':
- fs = FileShare.objects.get_file_link_by_path(username, repo_id, path)
- if fs is None:
- fs = FileShare.objects.create_file_link(username, repo_id, path,
- passwd, expire_date)
- if is_org_context(request):
- org_id = request.user.org.org_id
- OrgFileShare.objects.set_org_file_share(org_id, fs)
- else:
- fs = FileShare.objects.get_dir_link_by_path(username, repo_id, path)
- if fs is None:
- fs = FileShare.objects.create_dir_link(username, repo_id, path,
- passwd, expire_date)
- if is_org_context(request):
- org_id = request.user.org.org_id
- OrgFileShare.objects.set_org_file_share(org_id, fs)
-
- token = fs.token
- shared_link = gen_shared_link(token, fs.s_type)
- data = json.dumps({'token': token, 'download_link': shared_link})
- return HttpResponse(data, content_type=content_type)
@login_required_ajax
@require_POST
diff --git a/seahub/templates/home_base.html b/seahub/templates/home_base.html
index b2dd36080b..38e3b0f54f 100644
--- a/seahub/templates/home_base.html
+++ b/seahub/templates/home_base.html
@@ -50,10 +50,10 @@
{% trans "Share Admin" %}
{% endblock %}
diff --git a/seahub/templates/js/templates.html b/seahub/templates/js/templates.html
index f7146b72e1..45a5b2dd33 100644
--- a/seahub/templates/js/templates.html
+++ b/seahub/templates/js/templates.html
@@ -692,15 +692,15 @@
{% trans "Share Admin" %}
@@ -1502,3 +1502,184 @@
<% } %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/seahub/templates/snippets/shared_link_js.html b/seahub/templates/snippets/shared_link_js.html
index c02b68bbfb..423d1d09b7 100644
--- a/seahub/templates/snippets/shared_link_js.html
+++ b/seahub/templates/snippets/shared_link_js.html
@@ -1,24 +1,6 @@
{% load i18n %}
{% load url from future %}
-/*
-var share_list = [];
-$(function () {
- $.ajax({
- url:'{% url 'get_contacts' %}',
- cache: false,
- dataType: 'json',
- success: function(data) {
- var contact_list = data['contacts'], contact_email;
- for (var i = 0, len = contact_list.length; i < len; i++) {
- contact_email = contact_list[i].email;
- share_list.push({value: contact_email, label: contact_email});
- }
- }
- });
-});
-*/
-
function showSharePopup(op, name, aj_data, type, cur_path) {
var path = cur_path + name;
@@ -183,13 +165,13 @@ $('#gen-link-btn').click(function() {
}
$.ajax({
- url: '{% url 'ajax_get_download_link' %}',
+ url: '{% url 'api-v2.1-share-links' %}',
type: 'POST',
dataType: 'json',
beforeSend: prepareCSRFToken,
data: $.extend(post_data, $(this).data('aj_data')),
success: function(data) {
- var link = data['download_link'];
+ var link = data['link'];
// hide gen-link button, and show link
gen_link_btn.addClass('hide');
@@ -215,9 +197,8 @@ $('#rm-shared-link').click(function() {
token = obj.attr('data-token');
$.ajax({
- url: '{% url 'ajax_remove_shared_link' %}',
- type: 'POST',
- data: {'t': token},
+ url: '{{ SITE_ROOT }}api/v2.1/share-links/' + token + '/',
+ type: 'DELETE',
dataType: 'json',
cache: false,
beforeSend: prepareCSRFToken,
@@ -247,7 +228,7 @@ $('#link-passwd-switch').click(function () {
$('#link-expire-switch').click(function () {
var form = $('#link-options'),
days_input = $('input[name="expire-days"]', form);
- var link_expire = $('#link-expire');
+ var link_expire = $('#link-expire');
if ($(this).prop('checked')) {
link_expire.slideDown(100);
diff --git a/seahub/templates/snippets/sort_lib_js.html b/seahub/templates/snippets/sort_lib_js.html
deleted file mode 100644
index 2bc07e03f2..0000000000
--- a/seahub/templates/snippets/sort_lib_js.html
+++ /dev/null
@@ -1,11 +0,0 @@
-$(function() {
- $.ajax({
- url: '{{MEDIA_URL}}js/sort_lib.js',
- dataType: 'script',
- cache: true,
- success: function() {
- $("span[id$='repo-list-name-down'], span[id$='repo-list-name-up']").on('click',sort_lib_by_name);
- $("span[id$='repo-list-time-down'], span[id$='repo-list-time-up']").on('click',sort_lib_by_time);
- }
- });
-});
diff --git a/seahub/templates/view_file_base.html b/seahub/templates/view_file_base.html
index d23fd50688..f8e6894ce2 100644
--- a/seahub/templates/view_file_base.html
+++ b/seahub/templates/view_file_base.html
@@ -170,15 +170,16 @@ $(function() {
$('#share').click(function() {
var op = $(this),
name = "{{filename|escapejs}}",
- path = "{{path|escapejs}}";
+ path = "{{path|escapejs}}",
aj_data = {
'repo_id': "{{ repo.id }}",
- 'p': path,
- 'type': 'f'
+ 'path': path
},
type = 'f',
cur_path = path.substr(0, path.length - name.length);
+
showSharePopup(op, name, aj_data, type, cur_path);
+
return false;
});
diff --git a/seahub/test_utils.py b/seahub/test_utils.py
index 59b9ecd671..73d8f4e7e3 100644
--- a/seahub/test_utils.py
+++ b/seahub/test_utils.py
@@ -11,7 +11,7 @@ from django.utils.importlib import import_module
from exam.decorators import fixture
from exam.cases import Exam
import seaserv
-from seaserv import seafile_api, ccnet_threaded_rpc
+from seaserv import seafile_api, ccnet_threaded_rpc, ccnet_api
from seahub.base.accounts import User
from seahub.utils import mkstemp
@@ -139,6 +139,48 @@ class Fixtures(Exam):
group_id = self.group.id
return ccnet_threaded_rpc.remove_group(group_id, self.user.username)
+ def set_user_folder_r_permission_to_admin(self):
+
+ # share user's repo to admin with 'rw' permission
+ seafile_api.share_repo(self.repo.id, self.user.username,
+ self.admin.username, 'rw')
+
+ # set user sub-folder 'r' permisson to admin
+ seafile_api.add_folder_user_perm(self.repo.id,
+ self.folder, 'r', self.admin.username)
+
+ # admin can visit user sub-folder with 'r' permission
+ assert seafile_api.check_permission_by_path(self.repo.id,
+ self.folder, self.admin.username) == 'r'
+
+ def set_user_folder_rw_permission_to_admin(self):
+
+ # share user's repo to admin with 'r' permission
+ seafile_api.share_repo(self.repo.id, self.user.username,
+ self.admin.username, 'r')
+
+ # set user sub-folder 'rw' permisson to admin
+ seafile_api.add_folder_user_perm(self.repo.id,
+ self.folder, 'rw', self.admin.username)
+
+ # admin can visit user sub-folder with 'rw' permission
+ assert seafile_api.check_permission_by_path(self.repo.id,
+ self.folder, self.admin.username) == 'rw'
+
+ def share_repo_to_group_with_r_permission(self):
+ seafile_api.set_group_repo(
+ self.repo.id, self.group.id, self.user.username, 'r')
+
+ def share_repo_to_group_with_rw_permission(self):
+ seafile_api.set_group_repo(
+ self.repo.id, self.group.id, self.user.username, 'rw')
+
+ def add_admin_to_group(self):
+ ccnet_api.group_add_member(
+ self.group.id, self.user.username, self.admin.username)
+
+ assert ccnet_api.is_group_user(self.group.id, self.admin.username)
+
class BaseTestCase(TestCase, Fixtures):
def tearDown(self):
diff --git a/seahub/urls.py b/seahub/urls.py
index 88d6b1ae9c..053c6a2816 100644
--- a/seahub/urls.py
+++ b/seahub/urls.py
@@ -20,6 +20,8 @@ from seahub.views.ajax import *
from seahub.api2.endpoints.groups import Groups, Group
from seahub.api2.endpoints.group_members import GroupMembers, GroupMembersBulk, GroupMember
from seahub.api2.endpoints.share_links import ShareLinks, ShareLink
+from seahub.api2.endpoints.shared_folders import SharedFolders
+from seahub.api2.endpoints.shared_repos import SharedRepos, SharedRepo
from seahub.api2.endpoints.upload_links import UploadLinks, UploadLink
from seahub.api2.endpoints.file import FileView
from seahub.api2.endpoints.dir import DirView
@@ -76,7 +78,6 @@ urlpatterns = patterns(
url(r'^repo/history/revert/(?P[-0-9a-f]{36})/$', repo_revert_history, name='repo_revert_history'),
(r'^repo/upload_check/$', validate_filename),
- url(r'^repo/unsetinnerpub/(?P[-0-9a-f]{36})/$', unsetinnerpub, name='unsetinnerpub'),
url(r'^repo/download_dir/(?P[-0-9a-f]{36})/$', repo_download_dir, name='repo_download_dir'),
url(r'^repo/file_revisions/(?P[-0-9a-f]{36})/$', file_revisions, name='file_revisions'),
url(r'^repo/file-access/(?P[-0-9a-f]{36})/$', file_access, name='file_access'),
@@ -177,6 +178,9 @@ urlpatterns = patterns(
url(r'^api/v2.1/groups/(?P\d+)/members/$', GroupMembers.as_view(), name='api-v2.1-group-members'),
url(r'^api/v2.1/groups/(?P\d+)/members/bulk/$', GroupMembersBulk.as_view(), name='api-v2.1-group-members-bulk'),
url(r'^api/v2.1/groups/(?P\d+)/members/(?P[^/]+)/$', GroupMember.as_view(), name='api-v2.1-group-member'),
+ url(r'^api/v2.1/shared-folders/$', SharedFolders.as_view(), name='api-v2.1-shared-folders'),
+ url(r'^api/v2.1/shared-repos/$', SharedRepos.as_view(), name='api-v2.1-shared-repos'),
+ url(r'^api/v2.1/shared-repos/(?P[-0-9a-f]{36})/$', SharedRepo.as_view(), name='api-v2.1-shared-repo'),
url(r'^api/v2.1/share-links/$', ShareLinks.as_view(), name='api-v2.1-share-links'),
url(r'^api/v2.1/share-links/(?P[a-f0-9]{10})/$', ShareLink.as_view(), name='api-v2.1-share-link'),
url(r'^api/v2.1/upload-links/$', UploadLinks.as_view(), name='api-v2.1-upload-links'),
diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py
index 3d40d4eda0..434ceeff3e 100644
--- a/seahub/views/__init__.py
+++ b/seahub/views/__init__.py
@@ -825,80 +825,6 @@ def libraries(request):
'can_add_pub_repo': can_add_pub_repo,
}, context_instance=RequestContext(request))
-@login_required
-@require_POST
-def unsetinnerpub(request, repo_id):
- """Unshare repos in organization or in share admin page.
-
- Only system admin, organization admin or repo owner can perform this op.
- """
- repo = get_repo(repo_id)
- perm = request.GET.get('permission', None)
- if perm is None:
- return render_error(request, _(u'Argument is not valid'))
- if not repo:
- messages.error(request, _('Failed to unshare the library, as it does not exist.'))
- return HttpResponseRedirect(reverse('share_admin'))
-
- # permission check
- username = request.user.username
- if is_org_context(request):
- org_id = request.user.org.org_id
- repo_owner = seafile_api.get_org_repo_owner(repo.id)
- is_repo_owner = True if repo_owner == username else False
- if not (request.user.org.is_staff or is_repo_owner):
- raise Http404
- else:
- repo_owner = seafile_api.get_repo_owner(repo.id)
- is_repo_owner = True if repo_owner == username else False
- if not (request.user.is_staff or is_repo_owner):
- raise Http404
-
- try:
- if is_org_context(request):
- org_id = request.user.org.org_id
- seaserv.seafserv_threaded_rpc.unset_org_inner_pub_repo(org_id,
- repo.id)
- else:
- seaserv.unset_inner_pub_repo(repo.id)
-
- origin_repo_id, origin_path = get_origin_repo_info(repo.id)
- if origin_repo_id is not None:
- perm_repo_id = origin_repo_id
- perm_path = origin_path
- else:
- perm_repo_id = repo.id
- perm_path = '/'
-
- send_perm_audit_msg('delete-repo-perm', username, 'all',
- perm_repo_id, perm_path, perm)
-
- messages.success(request, _('Unshare "%s" successfully.') % repo.name)
- except SearpcError:
- messages.error(request, _('Failed to unshare "%s".') % repo.name)
-
- referer = request.META.get('HTTP_REFERER', None)
- next = settings.SITE_ROOT if referer is None else referer
-
- return HttpResponseRedirect(next)
-
-# @login_required
-# def ownerhome(request, owner_name):
-# owned_repos = []
-# quota_usage = 0
-
-# owned_repos = seafserv_threaded_rpc.list_owned_repos(owner_name)
-# quota_usage = seafserv_threaded_rpc.get_user_quota_usage(owner_name)
-
-# user_dict = user_info(request, owner_name)
-
-# return render_to_response('ownerhome.html', {
-# "owned_repos": owned_repos,
-# "quota_usage": quota_usage,
-# "owner": owner_name,
-# "user_dict": user_dict,
-# }, context_instance=RequestContext(request))
-
@login_required
def repo_set_access_property(request, repo_id):
ap = request.GET.get('ap', '')
diff --git a/static/scripts/app/collections/share-admin-folders.js b/static/scripts/app/collections/share-admin-folders.js
new file mode 100644
index 0000000000..136067586e
--- /dev/null
+++ b/static/scripts/app/collections/share-admin-folders.js
@@ -0,0 +1,20 @@
+define([
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/models/share-admin-folder'
+], function(_, Backbone, Common, ShareAdminFolder) {
+ 'use strict';
+
+ var ShareAdminFolderCollection = Backbone.Collection.extend({
+
+ model: ShareAdminFolder,
+
+ url: function() {
+ return Common.getUrl({name: 'share_admin_folders'});
+ }
+
+ });
+
+ return ShareAdminFolderCollection;
+});
diff --git a/static/scripts/app/collections/share-admin-repos.js b/static/scripts/app/collections/share-admin-repos.js
new file mode 100644
index 0000000000..5e8ceb6aec
--- /dev/null
+++ b/static/scripts/app/collections/share-admin-repos.js
@@ -0,0 +1,20 @@
+define([
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/models/share-admin-repo'
+], function(_, Backbone, Common, ShareAdminRepo) {
+ 'use strict';
+
+ var ShareAdminRepoCollection = Backbone.Collection.extend({
+
+ model: ShareAdminRepo,
+
+ url: function() {
+ return Common.getUrl({name: 'share_admin_repos'});
+ }
+
+ });
+
+ return ShareAdminRepoCollection;
+});
diff --git a/static/scripts/app/collections/share-admin-share-links.js b/static/scripts/app/collections/share-admin-share-links.js
new file mode 100644
index 0000000000..f27bc1e180
--- /dev/null
+++ b/static/scripts/app/collections/share-admin-share-links.js
@@ -0,0 +1,20 @@
+define([
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/models/share-admin-share-link'
+], function(_, Backbone, Common, ShareAdminShareLink) {
+ 'use strict';
+
+ var ShareAdminShareLinkCollection = Backbone.Collection.extend({
+
+ model: ShareAdminShareLink,
+
+ url: function() {
+ return Common.getUrl({name: 'share_admin_share_links'});
+ }
+
+ });
+
+ return ShareAdminShareLinkCollection;
+});
diff --git a/static/scripts/app/collections/share-admin-upload-links.js b/static/scripts/app/collections/share-admin-upload-links.js
new file mode 100644
index 0000000000..e8f1b55508
--- /dev/null
+++ b/static/scripts/app/collections/share-admin-upload-links.js
@@ -0,0 +1,20 @@
+define([
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/models/share-admin-upload-link'
+], function(_, Backbone, Common, ShareAdminUploadLink) {
+ 'use strict';
+
+ var ShareAdminUploadLinkCollection = Backbone.Collection.extend({
+
+ model: ShareAdminUploadLink,
+
+ url: function() {
+ return Common.getUrl({name: 'share_admin_upload_links'});
+ }
+
+ });
+
+ return ShareAdminUploadLinkCollection;
+});
diff --git a/static/scripts/app/models/share-admin-folder.js b/static/scripts/app/models/share-admin-folder.js
new file mode 100644
index 0000000000..73f1f80171
--- /dev/null
+++ b/static/scripts/app/models/share-admin-folder.js
@@ -0,0 +1,35 @@
+define([
+ 'underscore',
+ 'backbone',
+ 'common'
+], function(_, Backbone, Common) {
+ 'use strict';
+
+ var ShareAdminFolder = Backbone.Model.extend({
+
+ getWebUrl: function() {
+ var path = this.get('path'),
+ repo_id = this.get('repo_id');
+
+ return "#my-libs/lib/" + repo_id + Common.encodePath(path);
+ },
+
+ getIconUrl: function(size) {
+ var is_readonly = this.get('share_permission') == "r" ? true : false;
+ return Common.getDirIconUrl(is_readonly, size);
+ },
+
+ getIconTitle: function() {
+ var icon_title = '';
+ if (this.get('share_permission') == "rw") {
+ icon_title = gettext("Read-Write");
+ } else {
+ icon_title = gettext("Read-Only");
+ }
+ return icon_title;
+ }
+
+ });
+
+ return ShareAdminFolder;
+});
diff --git a/static/scripts/app/models/share-admin-repo.js b/static/scripts/app/models/share-admin-repo.js
new file mode 100644
index 0000000000..7d0dca4d3c
--- /dev/null
+++ b/static/scripts/app/models/share-admin-repo.js
@@ -0,0 +1,33 @@
+define([
+ 'underscore',
+ 'backbone',
+ 'common'
+], function(_, Backbone, Common) {
+ 'use strict';
+
+ var ShareAdminRepo = Backbone.Model.extend({
+
+ getWebUrl: function() {
+ return "#common/lib/" + this.get('repo_id') + "/";
+ },
+
+ getIconUrl: function(size) {
+ var is_readonly = this.get('share_permission') == "r" ? true : false;
+ return Common.getLibIconUrl(false, is_readonly, size);
+ },
+
+ getIconTitle: function() {
+ var icon_title = '';
+ if (this.get('share_permission') == "rw") {
+ icon_title = gettext("Read-Write");
+ } else {
+ icon_title = gettext("Read-Only");
+ }
+
+ return icon_title;
+ }
+
+ });
+
+ return ShareAdminRepo;
+});
diff --git a/static/scripts/app/models/share-admin-share-link.js b/static/scripts/app/models/share-admin-share-link.js
new file mode 100644
index 0000000000..9172e754fc
--- /dev/null
+++ b/static/scripts/app/models/share-admin-share-link.js
@@ -0,0 +1,45 @@
+define([
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'moment'
+], function(_, Backbone, Common, Moment) {
+ 'use strict';
+
+ var ShareAdminShareLink = Backbone.Model.extend({
+ parse: function(response) {
+ var attrs = _.clone(response),
+ expire_date = response.expire_date;
+
+ if (expire_date) {
+ attrs.expire_date_timestamp = Moment(expire_date).format('X');
+ } else {
+ attrs.expire_date_timestamp = 0;
+ }
+
+ return attrs;
+ },
+
+ getIconUrl: function(size) {
+ if (this.get('is_dir')) {
+ return Common.getDirIconUrl(false, size);
+ } else {
+ return Common.getFileIconUrl(this.get('obj_name'), size);
+ }
+ },
+
+ getWebUrl: function() {
+ var repo_id = this.get('repo_id');
+ var dirent_path = this.get('path');
+
+ if (this.get('is_dir')) {
+ return "#common/lib/" + repo_id + Common.encodePath(dirent_path);
+ } else {
+ return app.config.siteRoot + "lib/" + repo_id + "/file" + Common.encodePath(dirent_path);
+ }
+ }
+
+ });
+
+ return ShareAdminShareLink;
+});
diff --git a/static/scripts/app/models/share-admin-upload-link.js b/static/scripts/app/models/share-admin-upload-link.js
new file mode 100644
index 0000000000..c08b3f6e52
--- /dev/null
+++ b/static/scripts/app/models/share-admin-upload-link.js
@@ -0,0 +1,21 @@
+define([
+ 'underscore',
+ 'backbone',
+ 'common'
+], function(_, Backbone, Common) {
+ 'use strict';
+
+ var ShareAdminUploadLink = Backbone.Model.extend({
+ getIconUrl: function(size) {
+ return Common.getDirIconUrl(false , size);
+ },
+
+ getWebUrl: function() {
+ var repo_id = this.get('repo_id');
+ var dirent_path = this.get('path');
+ return "#common/lib/" + repo_id + Common.encodePath(dirent_path);
+ }
+ });
+
+ return ShareAdminUploadLink;
+});
diff --git a/static/scripts/app/router.js b/static/scripts/app/router.js
index 6e79cbae03..dbd656d4b8 100644
--- a/static/scripts/app/router.js
+++ b/static/scripts/app/router.js
@@ -11,14 +11,19 @@ define([
'app/views/organization',
'app/views/dir',
'app/views/starred-file',
- 'app/views/devices',
'app/views/activities',
+ 'app/views/devices',
+ 'app/views/share-admin-repos',
+ 'app/views/share-admin-folders',
+ 'app/views/share-admin-share-links',
+ 'app/views/share-admin-upload-links',
'app/views/notifications',
'app/views/account'
], function($, Backbone, Common, SideNavView, MyReposView,
- SharedReposView, GroupsView, GroupView,
- OrgView, DirView, StarredFileView, DevicesView, ActivitiesView,
- NotificationsView, AccountView) {
+ SharedReposView, GroupsView, GroupView, OrgView, DirView,
+ StarredFileView, ActivitiesView, DevicesView, ShareAdminReposView,
+ ShareAdminFoldersView, ShareAdminShareLinksView,
+ ShareAdminUploadLinksView, NotificationsView, AccountView) {
"use strict";
var Router = Backbone.Router.extend({
@@ -38,6 +43,10 @@ define([
'starred/': 'showStarredFile',
'activities/': 'showActivities',
'devices/': 'showDevices',
+ 'share-admin-libs/': 'showShareAdminRepos',
+ 'share-admin-folders/': 'showShareAdminFolders',
+ 'share-admin-share-links/': 'showShareAdminShareLinks',
+ 'share-admin-upload-links/': 'showShareAdminUploadLinks',
// Default
'*actions': 'showRepos'
},
@@ -62,6 +71,10 @@ define([
this.starredFileView = new StarredFileView();
this.devicesView = new DevicesView();
this.activitiesView = new ActivitiesView();
+ this.shareAdminReposView = new ShareAdminReposView();
+ this.shareAdminFoldersView = new ShareAdminFoldersView();
+ this.shareAdminShareLinksView = new ShareAdminShareLinksView();
+ this.shareAdminUploadLinksView = new ShareAdminUploadLinksView();
app.ui.notificationsView = this.notificationsView = new NotificationsView();
app.ui.accountView = this.accountView = new AccountView();
@@ -204,16 +217,40 @@ define([
this.sideNavView.setCurTab('starred');
},
+ showActivities: function() {
+ this.switchCurrentView(this.activitiesView);
+ this.activitiesView.show();
+ this.sideNavView.setCurTab('activities');
+ },
+
showDevices: function() {
this.switchCurrentView(this.devicesView);
this.devicesView.show();
this.sideNavView.setCurTab('devices');
},
- showActivities: function() {
- this.switchCurrentView(this.activitiesView);
- this.activitiesView.show();
- this.sideNavView.setCurTab('activities');
+ showShareAdminRepos: function() {
+ this.switchCurrentView(this.shareAdminReposView);
+ this.shareAdminReposView.show();
+ this.sideNavView.setCurTab('share-admin-repos');
+ },
+
+ showShareAdminFolders: function() {
+ this.switchCurrentView(this.shareAdminFoldersView);
+ this.shareAdminFoldersView.show();
+ this.sideNavView.setCurTab('share-admin-folders');
+ },
+
+ showShareAdminShareLinks: function() {
+ this.switchCurrentView(this.shareAdminShareLinksView);
+ this.shareAdminShareLinksView.show();
+ this.sideNavView.setCurTab('share-admin-links');
+ },
+
+ showShareAdminUploadLinks: function() {
+ this.switchCurrentView(this.shareAdminUploadLinksView);
+ this.shareAdminUploadLinksView.show();
+ this.sideNavView.setCurTab('share-admin-links');
}
});
diff --git a/static/scripts/app/views/group.js b/static/scripts/app/views/group.js
index 44174c7c61..e1466da5b4 100644
--- a/static/scripts/app/views/group.js
+++ b/static/scripts/app/views/group.js
@@ -170,19 +170,22 @@ define([
sortByName: function() {
this.$('.by-time .sort-icon').hide();
var repos = this.repos;
- var el = this.$('.by-name .sort-icon');
- repos.comparator = function(a, b) { // a, b: model
- var result = Common.compareTwoWord(a.get('name'), b.get('name'));
- if (el.hasClass('icon-caret-up')) {
+ var $el = this.$('.by-name .sort-icon');
+ if ($el.hasClass('icon-caret-up')) {
+ repos.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('name'), b.get('name'));
return -result;
- } else {
+ };
+ } else {
+ repos.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('name'), b.get('name'));
return result;
- }
- };
+ };
+ }
repos.sort();
this.$tableBody.empty();
repos.each(this.addOne, this);
- el.toggleClass('icon-caret-up icon-caret-down').show();
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
repos.comparator = null;
return false;
},
@@ -190,18 +193,20 @@ define([
sortByTime: function() {
this.$('.by-name .sort-icon').hide();
var repos = this.repos;
- var el = this.$('.by-time .sort-icon');
- repos.comparator = function(a, b) { // a, b: model
- if (el.hasClass('icon-caret-down')) {
+ var $el = this.$('.by-time .sort-icon');
+ if ($el.hasClass('icon-caret-down')) {
+ repos.comparator = function(a, b) { // a, b: model
return a.get('mtime') < b.get('mtime') ? 1 : -1;
- } else {
+ };
+ } else {
+ repos.comparator = function(a, b) { // a, b: model
return a.get('mtime') < b.get('mtime') ? -1 : 1;
- }
- };
+ };
+ }
repos.sort();
this.$tableBody.empty();
repos.each(this.addOne, this);
- el.toggleClass('icon-caret-up icon-caret-down').show();
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
repos.comparator = null;
return false;
},
diff --git a/static/scripts/app/views/myhome-repos.js b/static/scripts/app/views/myhome-repos.js
index dc030078f1..24407452f7 100644
--- a/static/scripts/app/views/myhome-repos.js
+++ b/static/scripts/app/views/myhome-repos.js
@@ -117,19 +117,22 @@ define([
sortByName: function() {
$('.by-time .sort-icon').hide();
var repos = this.repos;
- var el = $('.by-name .sort-icon', this.$table);
- repos.comparator = function(a, b) { // a, b: model
- var result = Common.compareTwoWord(a.get('name'), b.get('name'));
- if (el.hasClass('icon-caret-up')) {
+ var $el = $('.by-name .sort-icon', this.$table);
+ if ($el.hasClass('icon-caret-up')) {
+ repos.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('name'), b.get('name'));
return -result;
- } else {
+ };
+ } else {
+ repos.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('name'), b.get('name'));
return result;
- }
- };
+ };
+ }
repos.sort();
this.$tableBody.empty();
repos.each(this.addOne, this);
- el.toggleClass('icon-caret-up icon-caret-down').show();
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
repos.comparator = null;
return false;
},
@@ -137,18 +140,20 @@ define([
sortByTime: function() {
$('.by-name .sort-icon').hide();
var repos = this.repos;
- var el = $('.by-time .sort-icon', this.$table);
- repos.comparator = function(a, b) { // a, b: model
- if (el.hasClass('icon-caret-down')) {
+ var $el = $('.by-time .sort-icon', this.$table);
+ if ($el.hasClass('icon-caret-down')) {
+ repos.comparator = function(a, b) { // a, b: model
return a.get('mtime') < b.get('mtime') ? 1 : -1;
- } else {
+ };
+ } else {
+ repos.comparator = function(a, b) { // a, b: model
return a.get('mtime') < b.get('mtime') ? -1 : 1;
- }
- };
+ };
+ }
repos.sort();
this.$tableBody.empty();
repos.each(this.addOne, this);
- el.toggleClass('icon-caret-up icon-caret-down').show();
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
repos.comparator = null;
return false;
}
diff --git a/static/scripts/app/views/myhome-shared-repos.js b/static/scripts/app/views/myhome-shared-repos.js
index 8417ed5d09..50d7b2dc60 100644
--- a/static/scripts/app/views/myhome-shared-repos.js
+++ b/static/scripts/app/views/myhome-shared-repos.js
@@ -104,19 +104,22 @@ define([
sortByName: function() {
$('.by-time .sort-icon', this.$table).hide();
var repos = this.repos;
- var el = $('.by-name .sort-icon', this.$table);
- repos.comparator = function(a, b) { // a, b: model
- var result = Common.compareTwoWord(a.get('name'), b.get('name'));
- if (el.hasClass('icon-caret-up')) {
+ var $el = $('.by-name .sort-icon', this.$table);
+ if ($el.hasClass('icon-caret-up')) {
+ repos.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('name'), b.get('name'));
return -result;
- } else {
+ };
+ } else {
+ repos.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('name'), b.get('name'));
return result;
- }
- };
+ };
+ }
repos.sort();
this.$tableBody.empty();
repos.each(this.addOne, this);
- el.toggleClass('icon-caret-up icon-caret-down').show();
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
repos.comparator = null;
return false;
},
@@ -124,18 +127,20 @@ define([
sortByTime: function() {
$('.by-name .sort-icon', this.$table).hide();
var repos = this.repos;
- var el = $('.by-time .sort-icon', this.$table);
- repos.comparator = function(a, b) { // a, b: model
- if (el.hasClass('icon-caret-down')) {
+ var $el = $('.by-time .sort-icon', this.$table);
+ if ($el.hasClass('icon-caret-down')) {
+ repos.comparator = function(a, b) { // a, b: model
return a.get('mtime') < b.get('mtime') ? 1 : -1;
- } else {
+ };
+ } else {
+ repos.comparator = function(a, b) { // a, b: model
return a.get('mtime') < b.get('mtime') ? -1 : 1;
- }
- };
+ };
+ }
repos.sort();
this.$tableBody.empty();
repos.each(this.addOne, this);
- el.toggleClass('icon-caret-up icon-caret-down').show();
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
repos.comparator = null;
return false;
}
diff --git a/static/scripts/app/views/organization.js b/static/scripts/app/views/organization.js
index cde923e2c9..dddc918aef 100644
--- a/static/scripts/app/views/organization.js
+++ b/static/scripts/app/views/organization.js
@@ -125,19 +125,22 @@ define([
sortByName: function() {
$('.by-time .sort-icon', this.$table).hide();
var repos = this.repos;
- var el = $('.by-name .sort-icon', this.$table);
- repos.comparator = function(a, b) { // a, b: model
- var result = Common.compareTwoWord(a.get('name'), b.get('name'));
- if (el.hasClass('icon-caret-up')) {
+ var $el = $('.by-name .sort-icon', this.$table);
+ if ($el.hasClass('icon-caret-up')) {
+ repos.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('name'), b.get('name'));
return -result;
- } else {
+ };
+ } else {
+ repos.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('name'), b.get('name'));
return result;
- }
- };
+ };
+ }
repos.sort();
this.$tableBody.empty();
repos.each(this.addOne, this);
- el.toggleClass('icon-caret-up icon-caret-down').show();
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
repos.comparator = null;
return false;
},
@@ -145,18 +148,20 @@ define([
sortByTime: function() {
$('.by-name .sort-icon', this.$table).hide();
var repos = this.repos;
- var el = $('.by-time .sort-icon', this.$table);
- repos.comparator = function(a, b) { // a, b: model
- if (el.hasClass('icon-caret-down')) {
+ var $el = $('.by-time .sort-icon', this.$table);
+ if ($el.hasClass('icon-caret-down')) {
+ repos.comparator = function(a, b) { // a, b: model
return a.get('mtime') < b.get('mtime') ? 1 : -1;
- } else {
+ };
+ } else {
+ repos.comparator = function(a, b) { // a, b: model
return a.get('mtime') < b.get('mtime') ? -1 : 1;
- }
- };
+ };
+ }
repos.sort();
this.$tableBody.empty();
repos.each(this.addOne, this);
- el.toggleClass('icon-caret-up icon-caret-down').show();
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
repos.comparator = null;
return false;
}
diff --git a/static/scripts/app/views/share-admin-folder.js b/static/scripts/app/views/share-admin-folder.js
new file mode 100644
index 0000000000..c9f742a009
--- /dev/null
+++ b/static/scripts/app/views/share-admin-folder.js
@@ -0,0 +1,121 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/views/widgets/hl-item-view'
+], function($, _, Backbone, Common, HLItemView) {
+ 'use strict';
+
+ var ShareAdminFolderView = HLItemView.extend({
+
+ tagName: 'tr',
+
+ template: _.template($('#share-admin-folder-tmpl').html()),
+
+ events: {
+ 'click .perm-edit-icon': 'showPermSelect',
+ 'change .perm-select': 'updatePermission',
+ 'click .unshare': 'removeShare'
+ },
+
+ initialize: function(option) {
+ HLItemView.prototype.initialize.call(this);
+ this.listenTo(this.model, "change", this.render);
+ },
+
+ showPermSelect: function() {
+ this.$el.closest('table')
+ .find('.perm-select').hide().end()
+ .find('.cur-perm, .perm-edit-icon').show();
+
+ this.$('.cur-perm, .perm-edit-icon').hide();
+ this.$('.perm-select').show();
+
+ return false;
+ },
+
+ updatePermission: function() {
+ var _this = this;
+ var share_type = this.model.get('share_type');
+ var perm = this.$('.perm-select').val();
+ var url = Common.getUrl({
+ name: 'dir_shared_items',
+ repo_id: this.model.get('repo_id')
+ }) + '?p=' + encodeURIComponent(this.model.get('path'));
+
+ if (share_type == 'personal') {
+ url += '&share_type=user&username=' + encodeURIComponent(this.model.get('user_email'));
+ } else if (share_type == 'group') {
+ url += '&share_type=group&group_id=' + this.model.get('group_id');
+ }
+
+ $.ajax({
+ url: url,
+ method: 'POST',
+ dataType: 'json',
+ beforeSend: Common.prepareCSRFToken,
+ data: {
+ 'permission': perm
+ },
+ success: function() {
+ _this.model.set({'share_permission': perm});
+ Common.feedback(gettext("Successfully modified permission"), 'success');
+ },
+ error: function(xhr) {
+ Common.ajaxErrorHandler(xhr);
+ }
+ });
+ },
+
+ removeShare: function() {
+ var _this = this;
+ var share_type = this.model.get('share_type');
+ var url = Common.getUrl({
+ name: 'dir_shared_items',
+ repo_id: this.model.get('repo_id')
+ }) + '?p=' + Common.encodePath(this.model.get('path'));
+
+ if (share_type == 'personal') {
+ url += '&share_type=user&username=' + Common.encodePath(this.model.get('user_email'));
+ } else if (share_type == 'group') {
+ url += '&share_type=group&group_id=' + this.model.get('group_id');
+ }
+
+ $.ajax({
+ url: url,
+ type: 'DELETE',
+ beforeSend: Common.prepareCSRFToken,
+ success: function() {
+ _this.remove();
+ Common.feedback(gettext("Successfully deleted 1 item"), 'success');
+ },
+ error: function (xhr) {
+ Common.ajaxErrorHandler(xhr);
+ }
+ });
+
+ return false;
+ },
+
+ render: function() {
+ var obj = this.model.toJSON(),
+ icon_size = Common.isHiDPI() ? 96 : 24,
+ icon_url = this.model.getIconUrl(icon_size);
+
+ _.extend(obj, {
+ 'icon_url': icon_url,
+ 'icon_title': this.model.getIconTitle(),
+ 'url': this.model.getWebUrl(),
+ 'name': this.model.get('folder_name')
+ });
+
+ this.$el.html(this.template(obj));
+
+ return this;
+ }
+
+ });
+
+ return ShareAdminFolderView;
+});
diff --git a/static/scripts/app/views/share-admin-folders.js b/static/scripts/app/views/share-admin-folders.js
new file mode 100644
index 0000000000..4c66154f8c
--- /dev/null
+++ b/static/scripts/app/views/share-admin-folders.js
@@ -0,0 +1,136 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/collections/share-admin-folders',
+ 'app/views/share-admin-folder'
+], function($, _, Backbone, Common, ShareAdminFolderCollection, ShareAdminFolderView) {
+ 'use strict';
+
+ var ShareAdminFoldersView = Backbone.View.extend({
+
+ id: 'share-admin-folders',
+
+ template: _.template($('#share-admin-folders-tmpl').html()),
+
+ initialize: function() {
+ this.folders = new ShareAdminFolderCollection();
+ this.listenTo(this.folders, 'add', this.addOne);
+ this.listenTo(this.folders, 'reset', this.reset);
+ this.render();
+
+ var _this = this;
+ $(document).click(function(e) {
+ var target = e.target || event.srcElement;
+ var $select = _this.$('.perm-select:visible');
+ if ($select.length && !$select.is(target)) {
+ $select.hide();
+ $select.closest('tr').find('.cur-perm, .perm-edit-icon').show();
+ }
+ });
+ },
+
+ events: {
+ 'click .by-name': 'sortByName'
+ },
+
+ sortByName: function() {
+ var folders = this.folders;
+ var $el = this.$sortIcon;
+ if ($el.hasClass('icon-caret-up')) {
+ folders.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('folder_name'), b.get('folder_name'));
+ return -result;
+ };
+ } else {
+ folders.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('folder_name'), b.get('folder_name'));
+ return result;
+ };
+ }
+ folders.sort();
+ this.$tableBody.empty();
+ folders.each(this.addOne, this);
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
+ folders.comparator = null;
+ return false;
+ },
+
+ render: function() {
+ this.$el.html(this.template());
+ this.$table = this.$('table');
+ this.$sortIcon = $('.by-name .sort-icon', this.$table);
+ this.$tableBody = $('tbody', this.$table);
+ this.$loadingTip = this.$('.loading-tip');
+ this.$emptyTip = this.$('.empty-tips');
+ },
+
+ hide: function() {
+ this.$el.detach();
+ this.attached = false;
+ },
+
+ show: function() {
+ if (!this.attached) {
+ this.attached = true;
+ $("#right-panel").html(this.$el);
+ }
+ this.showContent();
+ },
+
+ showContent: function() {
+ this.initPage();
+ this.folders.fetch({
+ cache: false,
+ reset: true,
+ error: function(collection, response, opts) {
+ _this.$loadingTip.hide();
+ var $error = _this.$('.error');
+ var err_msg;
+ if (response.responseText) {
+ if (response['status'] == 401 || response['status'] == 403) {
+ err_msg = gettext("Permission error");
+ } else {
+ err_msg = gettext("Error");
+ }
+ } else {
+ err_msg = gettext('Please check the network.');
+ }
+ $error.html(err_msg).show();
+ }
+ });
+ },
+
+ initPage: function() {
+ this.$table.hide();
+ this.$sortIcon.attr('class', 'sort-icon icon-caret-down').hide();
+ this.$tableBody.empty();
+ this.$loadingTip.show();
+ this.$emptyTip.hide();
+ this.$('.error').hide();
+ },
+
+ reset: function() {
+ this.$('.error').hide();
+ this.$loadingTip.hide();
+ if (this.folders.length) {
+ this.$emptyTip.hide();
+ this.$tableBody.empty();
+ this.folders.each(this.addOne, this);
+ this.$table.show();
+ } else {
+ this.$table.hide();
+ this.$emptyTip.show();
+ }
+ },
+
+ addOne: function(folder) {
+ var view = new ShareAdminFolderView({model: folder});
+ this.$tableBody.append(view.render().el);
+ }
+
+ });
+
+ return ShareAdminFoldersView;
+});
diff --git a/static/scripts/app/views/share-admin-repo.js b/static/scripts/app/views/share-admin-repo.js
new file mode 100644
index 0000000000..9c848a48af
--- /dev/null
+++ b/static/scripts/app/views/share-admin-repo.js
@@ -0,0 +1,124 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/views/widgets/hl-item-view'
+], function($, _, Backbone, Common, HLItemView) {
+ 'use strict';
+
+ var ShareAdminRepoView = HLItemView.extend({
+
+ tagName: 'tr',
+
+ template: _.template($('#share-admin-folder-tmpl').html()),
+
+ events: {
+ 'click .perm-edit-icon': 'showPermSelect',
+ 'change .perm-select': 'updatePermission',
+ 'click .unshare': 'removeShare'
+ },
+
+ initialize: function(option) {
+ HLItemView.prototype.initialize.call(this);
+ this.listenTo(this.model, "change", this.render);
+ },
+
+ showPermSelect: function() {
+ this.$el.closest('table')
+ .find('.perm-select').hide().end()
+ .find('.cur-perm, .perm-edit-icon').show();
+
+ this.$('.cur-perm, .perm-edit-icon').hide();
+ this.$('.perm-select').show();
+
+ return false;
+ },
+
+ updatePermission: function() {
+ var _this = this;
+ var url = Common.getUrl({
+ 'name': 'share_admin_repo',
+ 'repo_id': this.model.get('repo_id')
+ });
+ var share_type = this.model.get('share_type');
+ var perm = this.$('.perm-select').val();
+ var data = {
+ 'share_type': share_type,
+ 'permission': perm
+ };
+ if (share_type == 'personal') {
+ data['user'] = this.model.get('user_email');
+ } else if (share_type == 'group') {
+ data['group_id'] = this.model.get('group_id');
+ }
+
+ $.ajax({
+ url: url,
+ method: 'PUT',
+ cache: false,
+ dataType: 'json',
+ data: data,
+ beforeSend: Common.prepareCSRFToken,
+ success: function() {
+ _this.model.set({'share_permission': perm});
+ Common.feedback(gettext("Successfully modified permission"), 'success');
+ },
+ error: function(xhr) {
+ Common.ajaxErrorHandler(xhr);
+ }
+ });
+ },
+
+ removeShare: function() {
+ var _this = this;
+ var share_type = this.model.get('share_type');
+ var url = Common.getUrl({
+ 'name': 'share_admin_repo',
+ 'repo_id': this.model.get('repo_id')
+ });
+
+ if (share_type == 'personal') {
+ url += '?share_type=personal&user=' + encodeURIComponent(this.model.get('user_email'));
+ } else if (share_type == 'group') {
+ url += '?share_type=group&group_id=' + this.model.get('group_id');
+ } else if (share_type == 'public') {
+ url += '?share_type=public';
+ }
+
+ $.ajax({
+ url: url,
+ type: 'DELETE',
+ beforeSend: Common.prepareCSRFToken,
+ success: function() {
+ _this.remove();
+ Common.feedback(gettext("Successfully deleted 1 item"), 'success');
+ },
+ error: function(xhr) {
+ Common.ajaxErrorHandler(xhr);
+ }
+ });
+
+ return false;
+ },
+
+ render: function() {
+ var obj = this.model.toJSON(),
+ icon_size = Common.isHiDPI() ? 96 : 24,
+ icon_url = this.model.getIconUrl(icon_size);
+
+ _.extend(obj, {
+ 'icon_url': icon_url,
+ 'icon_title': this.model.getIconTitle(),
+ 'url': this.model.getWebUrl(),
+ 'name': this.model.get('repo_name')
+ });
+
+ this.$el.html(this.template(obj));
+ return this;
+ }
+
+ });
+
+ return ShareAdminRepoView;
+});
diff --git a/static/scripts/app/views/share-admin-repos.js b/static/scripts/app/views/share-admin-repos.js
new file mode 100644
index 0000000000..63d94fec4a
--- /dev/null
+++ b/static/scripts/app/views/share-admin-repos.js
@@ -0,0 +1,138 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/collections/share-admin-repos',
+ 'app/views/share-admin-repo'
+], function($, _, Backbone, Common, ShareAdminRepoCollection, ShareAdminRepoView) {
+ 'use strict';
+
+ var ShareAdminReposView = Backbone.View.extend({
+
+ id: 'share-admin-repos',
+
+ template: _.template($('#share-admin-repos-tmpl').html()),
+
+ initialize: function() {
+ this.repos = new ShareAdminRepoCollection();
+ this.listenTo(this.repos, 'add', this.addOne);
+ this.listenTo(this.repos, 'reset', this.reset);
+ this.render();
+
+ var _this = this;
+ $(document).click(function(e) {
+ var target = e.target || event.srcElement;
+ var $select = _this.$('.perm-select:visible');
+ if ($select.length && !$select.is(target)) {
+ $select.hide();
+ $select.closest('tr').find('.cur-perm, .perm-edit-icon').show();
+ }
+ });
+ },
+
+ events: {
+ 'click .by-name': 'sortByName'
+ },
+
+ sortByName: function() {
+ var repos = this.repos;
+ var $el = this.$sortIcon;
+ if ($el.hasClass('icon-caret-up')) {
+ repos.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('repo_name'), b.get('repo_name'));
+ return -result;
+ };
+ } else {
+ repos.comparator = function(a, b) { // a, b: model
+ var result = Common.compareTwoWord(a.get('repo_name'), b.get('repo_name'));
+ return result;
+ };
+ }
+ repos.sort();
+ this.$tableBody.empty();
+ repos.each(this.addOne, this);
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
+ repos.comparator = null;
+ return false;
+ },
+
+ render: function() {
+ this.$el.html(this.template());
+ this.$table = this.$('table');
+ this.$sortIcon = $('.by-name .sort-icon', this.$table);
+ this.$tableBody = $('tbody', this.$table);
+ this.$loadingTip = this.$('.loading-tip');
+ this.$emptyTip = this.$('.empty-tips');
+
+ },
+
+ hide: function() {
+ this.$el.detach();
+ this.attached = false;
+ },
+
+ show: function() {
+ if (!this.attached) {
+ this.attached = true;
+ $("#right-panel").html(this.$el);
+ }
+ this.showContent();
+ },
+
+ showContent: function() {
+ var _this = this;
+ this.initPage();
+ this.repos.fetch({
+ cache: false,
+ reset: true,
+ error: function(collection, response, opts) {
+ _this.$loadingTip.hide();
+ var $error = _this.$('.error');
+ var err_msg;
+ if (response.responseText) {
+ if (response['status'] == 401 || response['status'] == 403) {
+ err_msg = gettext("Permission error");
+ } else {
+ err_msg = gettext("Error");
+ }
+ } else {
+ err_msg = gettext('Please check the network.');
+ }
+ $error.html(err_msg).show();
+ }
+ });
+ },
+
+ initPage: function() {
+ this.$table.hide();
+ this.$sortIcon.attr('class', 'sort-icon icon-caret-down').hide();
+ this.$tableBody.empty();
+ this.$loadingTip.show();
+ this.$emptyTip.hide();
+ this.$('.error').hide();
+ },
+
+ reset: function() {
+ this.$('.error').hide();
+ this.$loadingTip.hide();
+ if (this.repos.length) {
+ this.$emptyTip.hide();
+ this.$tableBody.empty();
+ this.repos.each(this.addOne, this);
+ this.$table.show();
+ } else {
+ this.$table.hide();
+ this.$emptyTip.show();
+ }
+ },
+
+ addOne: function(repo) {
+ var view = new ShareAdminRepoView({model: repo});
+ this.$tableBody.append(view.render().el);
+ }
+
+ });
+
+ return ShareAdminReposView;
+});
diff --git a/static/scripts/app/views/share-admin-share-link.js b/static/scripts/app/views/share-admin-share-link.js
new file mode 100644
index 0000000000..7006fd7548
--- /dev/null
+++ b/static/scripts/app/views/share-admin-share-link.js
@@ -0,0 +1,83 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'moment',
+ 'app/views/widgets/hl-item-view'
+], function($, _, Backbone, Common, Moment, HLItemView) {
+ 'use strict';
+
+ var ShareAdminShareLinkView = HLItemView.extend({
+
+ tagName: 'tr',
+
+ template: _.template($('#share-admin-download-link-tmpl').html()),
+ linkPopupTemplate: _.template($('#share-admin-link-popup-tmpl').html()),
+
+ events: {
+ 'click .rm-link': 'removeLink',
+ 'click .view-link': 'viewLink'
+ },
+
+ initialize: function(option) {
+ HLItemView.prototype.initialize.call(this);
+ },
+
+ viewLink: function() {
+ var $popup = $(this.linkPopupTemplate({'link': this.model.get('link')}));
+ $popup.modal({focus:false});
+ $('#simplemodal-container').css({'width':'auto', 'height':'auto'});
+
+ var $p = $('p', $popup),
+ $input = $('input', $popup);
+ $input.css({'width': $p.width() + 2});
+ $p.hide();
+ $input.show();
+ $input.click(function() {
+ $(this).select();
+ });
+ return false;
+ },
+
+ removeLink: function() {
+ var _this = this;
+
+ $.ajax({
+ url: Common.getUrl({
+ 'name': 'share_admin_share_link',
+ 'token': this.model.get('token')
+ }),
+ type: 'DELETE',
+ beforeSend: Common.prepareCSRFToken,
+ success: function() {
+ _this.remove();
+ Common.feedback(gettext("Successfully deleted 1 item"), 'success');
+ },
+ error: function(xhr) {
+ Common.ajaxErrorHandler(xhr);
+ }
+ });
+
+ return false;
+ },
+
+ render: function() {
+ var data = this.model.toJSON();
+ var icon_size = Common.isHiDPI() ? 96 : 24;
+ var icon_url = this.model.getIconUrl(icon_size);
+
+ _.extend(data, {
+ 'icon_url': icon_url,
+ 'dirent_url': this.model.getWebUrl(),
+ 'time': data['expire_date'] ? Moment(data['expire_date']).format('YYYY-MM-DD') : ''
+ });
+
+ this.$el.html(this.template(data));
+ return this;
+ }
+
+ });
+
+ return ShareAdminShareLinkView;
+});
diff --git a/static/scripts/app/views/share-admin-share-links.js b/static/scripts/app/views/share-admin-share-links.js
new file mode 100644
index 0000000000..db550d5452
--- /dev/null
+++ b/static/scripts/app/views/share-admin-share-links.js
@@ -0,0 +1,189 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/collections/share-admin-share-links',
+ 'app/views/share-admin-share-link'
+], function($, _, Backbone, Common, ShareAdminShareLinkCollection,
+ ShareAdminShareLinkView) {
+
+ 'use strict';
+
+ var ShareAdminShareLinksView = Backbone.View.extend({
+
+ id: 'share-admin-download-links',
+
+ template: _.template($('#share-admin-download-links-tmpl').html()),
+
+ initialize: function() {
+ this.links = new ShareAdminShareLinkCollection();
+ this.listenTo(this.links, 'add', this.addOne);
+ this.listenTo(this.links, 'reset', this.reset);
+ this.render();
+ },
+
+ events: {
+ 'click .by-name': 'sortByName',
+ 'click .by-time': 'sortByTime'
+ },
+
+ // initialSort: dirs come first
+ initialSort: function(a, b) { // a, b: model
+ var a_is_dir = a.get('is_dir'),
+ b_is_dir = b.get('is_dir');
+ if (a_is_dir && !b_is_dir) {
+ return -1;
+ } else if (!a_is_dir && b_is_dir) {
+ return 1;
+ } else {
+ return 0;
+ }
+ },
+
+ sortByName: function() {
+ var _this = this;
+ var links = this.links;
+ var $el = this.$sortByNameIcon;
+ this.$sortByTimeIcon.hide();
+ if ($el.hasClass('icon-caret-up')) {
+ links.comparator = function(a, b) { // a, b: model
+ var initialResult = _this.initialSort(a, b);
+ if (initialResult != 0) {
+ return initialResult;
+ } else {
+ var result = Common.compareTwoWord(a.get('obj_name'), b.get('obj_name'));
+ return -result;
+ }
+ };
+ } else {
+ links.comparator = function(a, b) { // a, b: model
+ var initialResult = _this.initialSort(a, b);
+ if (initialResult != 0) {
+ return initialResult;
+ } else {
+ var result = Common.compareTwoWord(a.get('obj_name'), b.get('obj_name'));
+ return result;
+ }
+ };
+ }
+ links.sort();
+ this.$tableBody.empty();
+ links.each(this.addOne, this);
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
+ links.comparator = null;
+ return false;
+ },
+
+ sortByTime: function() {
+ var _this = this;
+ var links = this.links;
+ var $el = this.$sortByTimeIcon;
+ this.$sortByNameIcon.hide();
+ if ($el.hasClass('icon-caret-down')) {
+ links.comparator = function(a, b) { // a, b: model
+ var initialResult = _this.initialSort(a, b);
+ if (initialResult != 0) {
+ return initialResult;
+ } else {
+ return a.get('expire_date_timestamp') < b.get('expire_date_timestamp') ? 1 : -1;
+ }
+ };
+ } else {
+ links.comparator = function(a, b) { // a, b: model
+ var initialResult = _this.initialSort(a, b);
+ if (initialResult != 0) {
+ return initialResult;
+ } else {
+ return a.get('expire_date_timestamp') < b.get('expire_date_timestamp') ? -1 : 1;
+ }
+ };
+ }
+ links.sort();
+ this.$tableBody.empty();
+ links.each(this.addOne, this);
+ $el.toggleClass('icon-caret-up icon-caret-down').show();
+ links.comparator = null;
+ return false;
+ },
+
+ render: function() {
+ this.$el.html(this.template());
+ this.$table = this.$('table');
+ this.$sortByNameIcon = this.$('.by-name .sort-icon');
+ this.$sortByTimeIcon = this.$('.by-time .sort-icon');
+ this.$tableBody = $('tbody', this.$table);
+ this.$loadingTip = this.$('.loading-tip');
+ this.$emptyTip = this.$('.empty-tips');
+ },
+
+ hide: function() {
+ this.$el.detach();
+ this.attached = false;
+ },
+
+ show: function() {
+ if (!this.attached) {
+ this.attached = true;
+ $("#right-panel").html(this.$el);
+ }
+ this.showContent();
+ },
+
+ showContent: function() {
+ var _this = this;
+ this.initPage();
+ this.links.fetch({
+ cache: false,
+ reset: true,
+ error: function(collection, response, opts) {
+ _this.$loadingTip.hide();
+ var $error = _this.$('.error');
+ var err_msg;
+ if (response.responseText) {
+ if (response['status'] == 401 || response['status'] == 403) {
+ err_msg = gettext("Permission error");
+ } else {
+ err_msg = gettext("Error");
+ }
+ } else {
+ err_msg = gettext('Please check the network.');
+ }
+ $error.html(err_msg).show();
+ }
+ });
+ },
+
+ initPage: function() {
+ this.$table.hide();
+ this.$sortByNameIcon.attr('class', 'sort-icon icon-caret-up').show();
+ this.$sortByTimeIcon.attr('class', 'sort-icon icon-caret-down').hide();
+ this.$tableBody.empty();
+ this.$loadingTip.show();
+ this.$emptyTip.hide();
+ this.$('.error').hide();
+ },
+
+ reset: function() {
+ this.$('.error').hide();
+ this.$loadingTip.hide();
+ if (this.links.length) {
+ this.$emptyTip.hide();
+ this.$tableBody.empty();
+ this.links.each(this.addOne, this);
+ this.$table.show();
+ } else {
+ this.$emptyTip.show();
+ this.$table.hide();
+ }
+ },
+
+ addOne: function(link) {
+ var view = new ShareAdminShareLinkView({model: link});
+ this.$tableBody.append(view.render().el);
+ }
+
+ });
+
+ return ShareAdminShareLinksView;
+});
diff --git a/static/scripts/app/views/share-admin-upload-link.js b/static/scripts/app/views/share-admin-upload-link.js
new file mode 100644
index 0000000000..cf6c5f84f7
--- /dev/null
+++ b/static/scripts/app/views/share-admin-upload-link.js
@@ -0,0 +1,81 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/views/widgets/hl-item-view'
+], function($, _, Backbone, Common, HLItemView) {
+ 'use strict';
+
+ var ShareAdminUploadLinkView = HLItemView.extend({
+
+ tagName: 'tr',
+
+ template: _.template($('#share-admin-upload-link-tmpl').html()),
+ linkPopupTemplate: _.template($('#share-admin-link-popup-tmpl').html()),
+
+ events: {
+ 'click .rm-link': 'removeLink',
+ 'click .view-link': 'viewLink'
+ },
+
+ initialize: function(option) {
+ HLItemView.prototype.initialize.call(this);
+ },
+
+ viewLink: function() {
+ var $popup = $(this.linkPopupTemplate({'link': this.model.get('link')}));
+ $popup.modal({focus:false});
+ $('#simplemodal-container').css({'width':'auto', 'height':'auto'});
+
+ var $p = $('p', $popup),
+ $input = $('input', $popup);
+ $input.css({'width': $p.width() + 2});
+ $p.hide();
+ $input.show();
+ $input.click(function() {
+ $(this).select();
+ });
+ return false;
+ },
+
+ removeLink: function() {
+ var _this = this;
+
+ $.ajax({
+ url: Common.getUrl({
+ 'name': 'share_admin_upload_link',
+ 'token': this.model.get('token')
+ }),
+ type: 'DELETE',
+ beforeSend: Common.prepareCSRFToken,
+ success: function() {
+ _this.remove();
+ Common.feedback(gettext("Successfully deleted 1 item"), 'success');
+ },
+ error: function(xhr) {
+ Common.ajaxErrorHandler(xhr);
+ }
+ });
+
+ return false;
+ },
+
+ render: function() {
+ var data = this.model.toJSON();
+ var icon_size = Common.isHiDPI() ? 96 : 24;
+ var icon_url = this.model.getIconUrl(icon_size);
+
+ _.extend(data, {
+ 'icon_url': icon_url,
+ 'dirent_url': this.model.getWebUrl()
+ });
+
+ this.$el.html(this.template(data));
+ return this;
+ }
+
+ });
+
+ return ShareAdminUploadLinkView;
+});
diff --git a/static/scripts/app/views/share-admin-upload-links.js b/static/scripts/app/views/share-admin-upload-links.js
new file mode 100644
index 0000000000..405539bc50
--- /dev/null
+++ b/static/scripts/app/views/share-admin-upload-links.js
@@ -0,0 +1,101 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'common',
+ 'app/collections/share-admin-upload-links',
+ 'app/views/share-admin-upload-link'
+], function($, _, Backbone, Common, ShareAdminUploadLinkCollection,
+ ShareAdminUploadLinkView) {
+
+ 'use strict';
+
+ var ShareAdminUploadLinksView = Backbone.View.extend({
+
+ id: 'share-admin-upload-links',
+
+ template: _.template($('#share-admin-upload-links-tmpl').html()),
+
+ initialize: function() {
+ this.links = new ShareAdminUploadLinkCollection();
+ this.listenTo(this.links, 'add', this.addOne);
+ this.listenTo(this.links, 'reset', this.reset);
+ this.render();
+ },
+
+ render: function() {
+ this.$el.html(this.template());
+ this.$table = this.$('table');
+ this.$tableBody = $('tbody', this.$table);
+ this.$loadingTip = this.$('.loading-tip');
+ this.$emptyTip = this.$('.empty-tips');
+ },
+
+ hide: function() {
+ this.$el.detach();
+ this.attached = false;
+ },
+
+ show: function() {
+ if (!this.attached) {
+ this.attached = true;
+ $("#right-panel").html(this.$el);
+ }
+ this.showContent();
+ },
+
+ showContent: function() {
+ var _this = this;
+ this.initPage();
+ this.links.fetch({
+ cache: false,
+ reset: true,
+ error: function(collection, response, opts) {
+ _this.$loadingTip.hide();
+ var $error = _this.$('.error');
+ var err_msg;
+ if (response.responseText) {
+ if (response['status'] == 401 || response['status'] == 403) {
+ err_msg = gettext("Permission error");
+ } else {
+ err_msg = gettext("Error");
+ }
+ } else {
+ err_msg = gettext('Please check the network.');
+ }
+ $error.html(err_msg).show();
+ }
+ });
+ },
+
+ initPage: function() {
+ this.$table.hide();
+ this.$tableBody.empty();
+ this.$loadingTip.show();
+ this.$emptyTip.hide();
+ this.$('.error').hide();
+ },
+
+ reset: function() {
+ this.$('.error').hide();
+ this.$loadingTip.hide();
+ if (this.links.length) {
+ this.$emptyTip.hide();
+ this.$tableBody.empty();
+ this.links.each(this.addOne, this);
+ this.$table.show();
+ } else {
+ this.$emptyTip.show();
+ this.$table.hide();
+ }
+ },
+
+ addOne: function(link) {
+ var view = new ShareAdminUploadLinkView({model: link});
+ this.$tableBody.append(view.render().el);
+ }
+
+ });
+
+ return ShareAdminUploadLinksView;
+});
diff --git a/static/scripts/app/views/share.js b/static/scripts/app/views/share.js
index 91bcad2f42..a3e01d0a59 100644
--- a/static/scripts/app/views/share.js
+++ b/static/scripts/app/views/share.js
@@ -105,16 +105,18 @@ define([
downloadLinkPanelInit: function() {
var _this = this;
- var after_op_success = function(data) {
+ var after_op_success = function(data) { // data is [] or [{...}]
_this.$('.loading-tip').hide();
- if (data['download_link']) {
- _this.download_link = data["download_link"]; // for 'link send'
- _this.download_link_token = data["token"]; // for 'link delete'
- _this.$('#download-link').html(data['download_link']);
- _this.$('#direct-dl-link').html(data['download_link']+'?raw=1');
- if (data['is_expired']) {
+ if (data.length == 1) {
+ var link_data = data[0],
+ link = link_data.link;
+ _this.download_link = link; // for 'link send'
+ _this.download_link_token = link_data.token; // for 'link delete'
+ _this.$('#download-link').html(link);
+ _this.$('#direct-dl-link').html(link + '?raw=1');
+ if (link_data.is_expired) {
_this.$('#send-download-link').addClass('hide');
_this.$('#download-link, #direct-dl-link').append(' (' + gettext('Expired') + ') ');
}
@@ -125,11 +127,10 @@ define([
};
// check if downloadLink exists
Common.ajaxGet({
- 'get_url': Common.getUrl({name: 'get_shared_download_link'}),
+ 'get_url': Common.getUrl({name: 'share_admin_share_links'}),
'data': {
'repo_id': this.repo_id,
- 'p': this.dirent_path,
- 'type': this.is_dir ? 'd' : 'f'
+ 'path': this.dirent_path
},
'after_op_success': after_op_success
});
@@ -239,13 +240,8 @@ define([
$.extend(post_data, {
'repo_id': this.repo_id,
- 'p': this.dirent_path
+ 'path': this.dirent_path
});
- if (link_type == 'download') {
- $.extend(post_data, {
- 'type': this.is_dir? 'd' : 'f'
- });
- }
var _this = this;
var after_op_success = function(data) {
@@ -269,14 +265,14 @@ define([
}
if (link_type == 'download') {
- _this.$('#download-link').html(data["download_link"]); // TODO: add 'click & select' func
- _this.$('#direct-dl-link').html(data['download_link'] + '?raw=1');
- _this.download_link = data["download_link"]; // for 'link send'
+ _this.$('#download-link').html(data["link"]); // TODO: add 'click & select' func
+ _this.$('#direct-dl-link').html(data['link'] + '?raw=1');
+ _this.download_link = data["link"]; // for 'link send'
_this.download_link_token = data["token"]; // for 'link delete'
_this.$('#download-link-operations').removeClass('hide');
} else {
- _this.$('#upload-link').html(data["upload_link"]);
- _this.upload_link = data["upload_link"];
+ _this.$('#upload-link').html(data["link"]);
+ _this.upload_link = data["link"];
_this.upload_link_token = data["token"];
_this.$('#upload-link-operations').removeClass('hide');
}
@@ -295,7 +291,7 @@ define([
this.generateLink({
link_type: 'download',
form: this.$('#generate-download-link-form'),
- post_url: Common.getUrl({name: 'get_shared_download_link'})
+ post_url: Common.getUrl({name: 'share_admin_share_links'})
});
return false;
},
@@ -384,9 +380,12 @@ define([
deleteDownloadLink: function() {
var _this = this;
$.ajax({
- url: Common.getUrl({name: 'delete_shared_download_link'}),
- type: 'POST',
- data: { 't': this.download_link_token },
+ url: Common.getUrl({
+ 'name': 'share_admin_share_link',
+ 'token': this.download_link_token
+ }),
+ type: 'DELETE',
+ cache: false,
beforeSend: Common.prepareCSRFToken,
dataType: 'json',
success: function(data) {
@@ -398,11 +397,13 @@ define([
uploadLinkPanelInit: function() {
var _this = this;
- var after_op_success = function(data) {
- if (data['upload_link']) {
- _this.upload_link_token = data["token"];
- _this.upload_link = data["upload_link"];
- _this.$('#upload-link').html(data["upload_link"]); // TODO
+ var after_op_success = function(data) { // data is [] or [{...}]
+ if (data.length == 1) {
+ var link_data = data[0],
+ link = link_data.link;
+ _this.upload_link_token = link_data.token;
+ _this.upload_link = link;
+ _this.$('#upload-link').html(link);
_this.$('#upload-link-operations').removeClass('hide');
} else {
_this.$('#generate-upload-link-form').removeClass('hide');
@@ -410,8 +411,11 @@ define([
};
// check if upload link exists
Common.ajaxGet({
- 'get_url': Common.getUrl({name: 'get_share_upload_link'}), // TODO
- 'data': {'repo_id': this.repo_id, 'p': this.dirent_path},
+ 'get_url': Common.getUrl({name: 'share_admin_upload_links'}),
+ 'data': {
+ 'repo_id': this.repo_id,
+ 'path': this.dirent_path
+ },
'after_op_success': after_op_success
});
},
@@ -428,7 +432,7 @@ define([
this.generateLink({
link_type: 'upload',
form: this.$('#generate-upload-link-form'),
- post_url: Common.getUrl({name: 'get_share_upload_link'})
+ post_url: Common.getUrl({name: 'share_admin_upload_links'})
});
return false;
},
@@ -458,9 +462,12 @@ define([
deleteUploadLink: function() {
var _this = this;
$.ajax({
- url: Common.getUrl({name: 'delete_shared_upload_link'}),
- type: 'POST',
- data: { 't': this.upload_link_token },
+ url: Common.getUrl({
+ 'name': 'share_admin_upload_link',
+ 'token': this.upload_link_token
+ }),
+ type: 'DELETE',
+ cache: false,
beforeSend: Common.prepareCSRFToken,
dataType: 'json',
success: function(data) {
diff --git a/static/scripts/common.js b/static/scripts/common.js
index 4d2087dce3..b6e3e89790 100644
--- a/static/scripts/common.js
+++ b/static/scripts/common.js
@@ -119,17 +119,22 @@ define([
case 'repo_group_folder_perm': return siteRoot + 'api2/repos/' + options.repo_id + '/group-folder-perm/';
case 'repo_change_password': return siteRoot + 'ajax/repo/' + options.repo_id + '/setting/change-passwd/';
+ // Share admin
+ case 'share_admin_repos': return siteRoot + 'api/v2.1/shared-repos/';
+ case 'share_admin_repo': return siteRoot + 'api/v2.1/shared-repos/' + options.repo_id + '/';
+ case 'share_admin_folders': return siteRoot + 'api/v2.1/shared-folders/';
+ case 'share_admin_share_links': return siteRoot + 'api/v2.1/share-links/';
+ case 'share_admin_share_link': return siteRoot + 'api/v2.1/share-links/' + options.token + '/';
+ case 'share_admin_upload_links': return siteRoot + 'api/v2.1/upload-links/';
+ case 'share_admin_upload_link': return siteRoot + 'api/v2.1/upload-links/' + options.token + '/';
+
// Permission
case 'set_user_folder_perm': return siteRoot + 'ajax/repo/' + options.repo_id + '/set-user-folder-perm/';
case 'set_group_folder_perm': return siteRoot + 'ajax/repo/' + options.repo_id + '/set-group-folder-perm/';
// Links
- case 'get_shared_download_link': return siteRoot + 'share/ajax/get-download-link/';
- case 'delete_shared_download_link': return siteRoot + 'share/ajax/link/remove/';
case 'send_shared_download_link': return siteRoot + 'share/link/send/';
case 'send_shared_upload_link': return siteRoot + 'share/upload_link/send/';
- case 'delete_shared_upload_link': return siteRoot + 'share/ajax/upload_link/remove/';
- case 'get_share_upload_link': return siteRoot + 'share/ajax/get-upload-link/';
// Group
case 'groups': return siteRoot + 'api/v2.1/groups/';
@@ -157,6 +162,7 @@ define([
case 'search_user': return siteRoot + 'api2/search-user/';
case 'user_profile': return siteRoot + 'profile/' + options.username + '/';
case 'space_and_traffic': return siteRoot + 'ajax/space_and_traffic/';
+
// sysadmin
case 'sysinfo': return siteRoot + 'api/v2.1/admin/sysinfo/';
case 'admin-devices': return siteRoot + 'api/v2.1/admin/devices/';
diff --git a/tests/api/endpoints/test_share_links.py b/tests/api/endpoints/test_share_links.py
index acb6829a43..3eb9ec5dc9 100644
--- a/tests/api/endpoints/test_share_links.py
+++ b/tests/api/endpoints/test_share_links.py
@@ -7,6 +7,11 @@ from seahub.test_utils import BaseTestCase
from seahub.share.models import FileShare
from seahub.api2.endpoints.share_links import ShareLinks, ShareLink
+try:
+ from seahub.settings import LOCAL_PRO_DEV_ENV
+except ImportError:
+ LOCAL_PRO_DEV_ENV = False
+
class ShareLinksTest(BaseTestCase):
def setUp(self):
@@ -34,7 +39,6 @@ class ShareLinksTest(BaseTestCase):
link = FileShare.objects.get(token=token)
link.delete()
- # test file share link
def test_get_file_share_link(self):
self.login_as(self.user)
token = self._add_file_share_link()
@@ -55,34 +59,6 @@ class ShareLinksTest(BaseTestCase):
self._remove_share_link(token)
- def test_create_file_share_link(self):
- self.login_as(self.user)
-
- resp = self.client.post(self.url, {'path': self.file_path, 'repo_id': self.repo_id})
- self.assertEqual(200, resp.status_code)
-
- json_resp = json.loads(resp.content)
- assert json_resp['link'] is not None
- assert json_resp['token'] is not None
- assert json_resp['is_expired'] is not None
-
- assert json_resp['token'] in json_resp['link']
- assert 'f' in json_resp['link']
-
- self._remove_share_link(json_resp['token'])
-
- def test_delete_file_share_link(self):
- self.login_as(self.user)
- token = self._add_file_share_link()
-
- url = reverse('api-v2.1-share-link', args=[token])
- resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
- self.assertEqual(200, resp.status_code)
-
- json_resp = json.loads(resp.content)
- assert json_resp['success'] is True
-
- # test dir share link
def test_get_dir_share_link(self):
self.login_as(self.user)
token = self._add_dir_share_link()
@@ -103,6 +79,30 @@ class ShareLinksTest(BaseTestCase):
self._remove_share_link(token)
+ @patch.object(ShareLinks, '_can_generate_shared_link')
+ def test_get_link_with_invalid_user_role_permission(self, mock_can_generate_shared_link):
+ self.login_as(self.user)
+ mock_can_generate_shared_link.return_value = False
+
+ resp = self.client.get(self.url)
+ self.assertEqual(403, resp.status_code)
+
+ def test_create_file_share_link(self):
+ self.login_as(self.user)
+
+ resp = self.client.post(self.url, {'path': self.file_path, 'repo_id': self.repo_id})
+ self.assertEqual(200, resp.status_code)
+
+ json_resp = json.loads(resp.content)
+ assert json_resp['link'] is not None
+ assert json_resp['token'] is not None
+ assert json_resp['is_expired'] is not None
+
+ assert json_resp['token'] in json_resp['link']
+ assert 'f' in json_resp['link']
+
+ self._remove_share_link(json_resp['token'])
+
def test_create_dir_share_link(self):
self.login_as(self.user)
@@ -119,6 +119,80 @@ class ShareLinksTest(BaseTestCase):
self._remove_share_link(json_resp['token'])
+ def test_create_link_with_invalid_repo_permission(self):
+ # login with admin to create share link in user repo
+ self.login_as(self.admin)
+ data = {'path': self.file_path, 'repo_id': self.repo_id}
+ resp = self.client.post(self.url, data)
+ self.assertEqual(403, resp.status_code)
+
+ def test_create_link_with_rw_permission_folder(self):
+
+ if not LOCAL_PRO_DEV_ENV:
+ return
+
+ self.set_user_folder_rw_permission_to_admin()
+
+ # login with admin to create share link for 'r' permission folder
+ self.login_as(self.admin)
+ data = {'path': self.file_path, 'repo_id': self.repo_id}
+ resp = self.client.post(self.url, data)
+ self.assertEqual(200, resp.status_code)
+
+ def test_create_link_with_rw_permission_folder_in_group(self):
+
+ self.share_repo_to_group_with_rw_permission()
+ self.add_admin_to_group()
+
+ # login with admin to create share link for 'r' permission folder
+ self.login_as(self.admin)
+ data = {'path': self.file_path, 'repo_id': self.repo_id}
+ resp = self.client.post(self.url, data)
+ self.assertEqual(200, resp.status_code)
+
+ def test_create_link_with_r_permission_folder(self):
+
+ if not LOCAL_PRO_DEV_ENV:
+ return
+
+ self.set_user_folder_r_permission_to_admin()
+
+ # login with admin to create share link for 'r' permission folder
+ self.login_as(self.admin)
+ data = {'path': self.file_path, 'repo_id': self.repo_id}
+ resp = self.client.post(self.url, data)
+ self.assertEqual(200, resp.status_code)
+
+ def test_create_link_with_r_permission_folder_in_group(self):
+
+ self.share_repo_to_group_with_r_permission()
+ self.add_admin_to_group()
+
+ # login with admin to create share link for 'r' permission folder
+ self.login_as(self.admin)
+ data = {'path': self.file_path, 'repo_id': self.repo_id}
+ resp = self.client.post(self.url, data)
+ self.assertEqual(200, resp.status_code)
+
+ @patch.object(ShareLinks, '_can_generate_shared_link')
+ def test_create_link_with_invalid_urer_role_permission(self, mock_can_generate_shared_link):
+ self.login_as(self.user)
+ mock_can_generate_shared_link.return_value = False
+
+ resp = self.client.post(self.url, {'path': self.folder_path, 'repo_id': self.repo_id})
+ self.assertEqual(403, resp.status_code)
+
+ def test_delete_file_share_link(self):
+ self.login_as(self.user)
+ token = self._add_file_share_link()
+
+ url = reverse('api-v2.1-share-link', args=[token])
+ resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
+ self.assertEqual(200, resp.status_code)
+
+ json_resp = json.loads(resp.content)
+ assert json_resp['success'] is True
+
def test_delete_dir_share_link(self):
self.login_as(self.user)
token = self._add_file_share_link()
@@ -129,42 +203,20 @@ class ShareLinksTest(BaseTestCase):
json_resp = json.loads(resp.content)
assert json_resp['success'] is True
- # test permission
- def test_can_not_delete_link_if_not_owner(self):
+ def test_delete_link_if_not_owner(self):
self.login_as(self.admin)
token = self._add_file_share_link()
url = reverse('api-v2.1-share-link', args=[token])
resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
self.assertEqual(403, resp.status_code)
- @patch.object(ShareLinks, '_can_generate_shared_link')
- def test_can_not_get_and_create_link_with_invalid_permission(self, mock_can_generate_shared_link):
- self.login_as(self.user)
- mock_can_generate_shared_link.return_value = False
-
- resp = self.client.get(self.url)
- self.assertEqual(403, resp.status_code)
-
- resp = self.client.post(self.url)
- self.assertEqual(403, resp.status_code)
-
- self.logout()
-
- # login with another user to test repo permission
- self.login_as(self.admin)
- mock_can_generate_shared_link.return_value = True
-
- args = '?repo_id=%s' % self.repo_id
- resp = self.client.get(self.url + args)
- self.assertEqual(403, resp.status_code)
-
- data = {'path': self.file_path, 'repo_id': self.repo_id}
- resp = self.client.post(self.url, data)
- self.assertEqual(403, resp.status_code)
-
@patch.object(ShareLink, '_can_generate_shared_link')
- def test_can_not_delete_link_with_invalid_permission(self, mock_can_generate_shared_link):
+ def test_delete_link_with_invalid_user_repo_permission(self, mock_can_generate_shared_link):
token = self._add_file_share_link()
+
+ self.login_as(self.user)
+ mock_can_generate_shared_link.return_value = False
+
url = reverse('api-v2.1-share-link', args=[token])
resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
self.assertEqual(403, resp.status_code)
diff --git a/tests/api/endpoints/test_shared_folders.py b/tests/api/endpoints/test_shared_folders.py
new file mode 100644
index 0000000000..0beb306491
--- /dev/null
+++ b/tests/api/endpoints/test_shared_folders.py
@@ -0,0 +1,60 @@
+import os
+import json
+
+from django.core.urlresolvers import reverse
+
+from seaserv import seafile_api
+
+from seahub.test_utils import BaseTestCase
+
+class SharedFoldersTest(BaseTestCase):
+
+ def create_virtual_repo(self):
+
+ name = os.path.basename(self.folder.rstrip('/'))
+ sub_repo_id = seafile_api.create_virtual_repo(
+ self.repo.id, self.folder, name,
+ name, self.user.username)
+ return sub_repo_id
+
+ def share_repo_to_user(self, repo_id):
+ seafile_api.share_repo(
+ repo_id, self.user.username,
+ self.admin.username, 'rw')
+
+ def share_repo_to_group(self, repo_id):
+ seafile_api.set_group_repo(
+ repo_id, self.group.id,
+ self.user.username, 'rw')
+
+ def setUp(self):
+ self.repo_id = self.repo.id
+ self.group_id = self.group.id
+ self.user_name = self.user.username
+ self.admin_user = self.admin.username
+ self.url = reverse('api-v2.1-shared-folders')
+
+ sub_repo_id = self.create_virtual_repo()
+ self.share_repo_to_user(sub_repo_id)
+ self.share_repo_to_group(sub_repo_id)
+
+ def tearDown(self):
+ self.remove_repo()
+
+ def test_can_get(self):
+ self.login_as(self.user)
+ resp = self.client.get(self.url)
+ self.assertEqual(200, resp.status_code)
+
+ json_resp = json.loads(resp.content)
+ assert json_resp[0]['share_type'] == 'personal'
+ assert json_resp[1]['share_type'] == 'group'
+
+ def test_get_with_invalid_repo_permission(self):
+ # login with admin, then get user's share repo info
+ self.login_as(self.admin)
+ resp = self.client.get(self.url)
+ self.assertEqual(200, resp.status_code)
+
+ json_resp = json.loads(resp.content)
+ assert len(json_resp) == 0
diff --git a/tests/api/endpoints/test_shared_repos.py b/tests/api/endpoints/test_shared_repos.py
new file mode 100644
index 0000000000..5acc8e99dd
--- /dev/null
+++ b/tests/api/endpoints/test_shared_repos.py
@@ -0,0 +1,210 @@
+import json
+
+from django.core.urlresolvers import reverse
+
+import seaserv
+from seaserv import seafile_api
+
+from seahub.test_utils import BaseTestCase
+
+class SharedReposTest(BaseTestCase):
+
+ def share_repo_to_user(self):
+ seafile_api.share_repo(
+ self.repo.id, self.user.username,
+ self.admin.username, 'rw')
+
+ def share_repo_to_group(self):
+ seafile_api.set_group_repo(
+ self.repo.id, self.group.id,
+ self.user.username, 'rw')
+
+ def share_repo_to_public(self):
+ seafile_api.add_inner_pub_repo(
+ self.repo.id, 'rw')
+
+ def setUp(self):
+ self.repo_id = self.repo.id
+ self.group_id = self.group.id
+ self.user_name = self.user.username
+ self.admin_name = self.admin.username
+ self.url = reverse('api-v2.1-shared-repos')
+
+ def tearDown(self):
+ self.remove_repo()
+
+ def test_can_get(self):
+ self.share_repo_to_user()
+ self.share_repo_to_group()
+ self.share_repo_to_public()
+
+ self.login_as(self.user)
+ resp = self.client.get(self.url)
+ self.assertEqual(200, resp.status_code)
+
+ json_resp = json.loads(resp.content)
+ assert json_resp[0]['share_type'] == 'personal'
+ assert json_resp[1]['share_type'] == 'group'
+ assert json_resp[2]['share_type'] == 'public'
+
+ def test_get_with_invalid_repo_permission(self):
+ self.share_repo_to_user()
+ self.share_repo_to_group()
+ self.share_repo_to_public()
+
+ # login with admin, then get user's share repo info
+ self.login_as(self.admin)
+ resp = self.client.get(self.url)
+ self.assertEqual(200, resp.status_code)
+
+ json_resp = json.loads(resp.content)
+ assert len(json_resp) == 0
+
+ def test_can_update_user_share_perm(self):
+ self.share_repo_to_user()
+
+ assert seafile_api.check_permission_by_path(
+ self.repo_id, '/', self.admin_name) == 'rw'
+
+ self.login_as(self.user)
+
+ url = reverse('api-v2.1-shared-repo', args=[self.repo_id])
+ data = 'permission=r&share_type=personal&user=%s' % self.admin_name
+ resp = self.client.put(url, data, 'application/x-www-form-urlencoded')
+
+ self.assertEqual(200, resp.status_code)
+ assert seafile_api.check_permission_by_path(
+ self.repo_id, '/', self.admin_name) == 'r'
+
+ def test_can_update_group_share_perm(self):
+ self.share_repo_to_group()
+
+# print seafile_api.get_folder_group_perm(self.repo_id, '/', int(self.group_id))
+
+ repos = seafile_api.get_group_repos_by_owner(self.user_name)
+ assert repos[0].permission == 'rw'
+
+ self.login_as(self.user)
+
+ url = reverse('api-v2.1-shared-repo', args=[self.repo_id])
+ data = 'permission=r&share_type=group&group_id=%s' % self.group_id
+ resp = self.client.put(url, data, 'application/x-www-form-urlencoded')
+
+ self.assertEqual(200, resp.status_code)
+
+ repos = seafile_api.get_group_repos_by_owner(self.user_name)
+ assert repos[0].permission == 'r'
+
+ def test_can_update_public_share_perm(self):
+ for r in seaserv.seafserv_threaded_rpc.list_inner_pub_repos():
+ seafile_api.remove_inner_pub_repo(r.repo_id)
+
+ self.share_repo_to_public()
+
+ repos = seaserv.seafserv_threaded_rpc.list_inner_pub_repos_by_owner(
+ self.user_name)
+ assert repos[0].permission == 'rw'
+
+ self.login_as(self.user)
+
+ url = reverse('api-v2.1-shared-repo', args=[self.repo_id])
+ data = 'permission=r&share_type=public'
+ resp = self.client.put(url, data, 'application/x-www-form-urlencoded')
+
+ self.assertEqual(200, resp.status_code)
+
+ repos = seaserv.seafserv_threaded_rpc.list_inner_pub_repos_by_owner(
+ self.user_name)
+ assert repos[0].permission == 'r'
+
+ def test_delete_user_share(self):
+ self.share_repo_to_user()
+
+ # admin user can view repo
+ assert seafile_api.check_permission_by_path(
+ self.repo_id, '/', self.admin_name) == 'rw'
+
+ self.login_as(self.user)
+
+ args = '?share_type=personal&user=%s' % self.admin_name
+ url = reverse('api-v2.1-shared-repo', args=[self.repo_id]) + args
+ resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
+
+ self.assertEqual(200, resp.status_code)
+
+ # admin user can NOT view repo
+ assert seafile_api.check_permission_by_path(
+ self.repo_id, '/', self.admin_name) == None
+
+ def test_delete_group_share(self):
+ self.share_repo_to_group()
+
+ # repo in group
+ repos = seafile_api.get_group_repos_by_owner(self.user_name)
+ assert repos[0].permission == 'rw'
+
+ self.login_as(self.user)
+
+ args = '?share_type=group&group_id=%s' % self.group_id
+ url = reverse('api-v2.1-shared-repo', args=[self.repo_id]) + args
+ resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
+
+ self.assertEqual(200, resp.status_code)
+
+ # repo NOT in group
+ repos = seafile_api.get_group_repos_by_owner(self.user_name)
+ assert len(repos) == 0
+
+ def test_delete_public_share(self):
+ for r in seaserv.seafserv_threaded_rpc.list_inner_pub_repos():
+ seafile_api.remove_inner_pub_repo(r.repo_id)
+
+ self.share_repo_to_public()
+
+ # repo in public
+ repos = seaserv.seafserv_threaded_rpc.list_inner_pub_repos_by_owner(
+ self.user_name)
+ assert repos[0].permission == 'rw'
+
+ self.login_as(self.user)
+
+ args = '?share_type=public'
+ url = reverse('api-v2.1-shared-repo', args=[self.repo_id]) + args
+ resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
+
+ self.assertEqual(200, resp.status_code)
+
+ # repo NOT in public
+ repos = seaserv.seafserv_threaded_rpc.list_inner_pub_repos_by_owner(
+ self.user_name)
+ assert len(repos) == 0
+
+ def test_update_perm_if_not_owner(self):
+ self.share_repo_to_user()
+
+ # admin can view repo but NOT owner
+ assert seafile_api.check_permission_by_path(
+ self.repo_id, '/', self.admin_name) == 'rw'
+
+ self.login_as(self.admin)
+
+ url = reverse('api-v2.1-shared-repo', args=[self.repo_id])
+ data = 'permission=r&share_type=personal'
+ resp = self.client.put(url, data, 'application/x-www-form-urlencoded')
+
+ self.assertEqual(403, resp.status_code)
+
+ def test_delete_perm_if_not_owner(self):
+ self.share_repo_to_user()
+
+ # admin can view repo but NOT owner
+ assert seafile_api.check_permission_by_path(
+ self.repo_id, '/', self.admin_name) == 'rw'
+
+ self.login_as(self.admin)
+
+ args = '?share_type=personal&user=%s' % self.admin_name
+ url = reverse('api-v2.1-shared-repo', args=[self.repo_id]) + args
+ resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
+
+ self.assertEqual(403, resp.status_code)
diff --git a/tests/api/endpoints/test_upload_links.py b/tests/api/endpoints/test_upload_links.py
index 30bcce7744..911bd4a050 100644
--- a/tests/api/endpoints/test_upload_links.py
+++ b/tests/api/endpoints/test_upload_links.py
@@ -7,6 +7,11 @@ from seahub.test_utils import BaseTestCase
from seahub.share.models import UploadLinkShare
from seahub.api2.endpoints.upload_links import UploadLinks, UploadLink
+try:
+ from seahub.settings import LOCAL_PRO_DEV_ENV
+except ImportError:
+ LOCAL_PRO_DEV_ENV = False
+
class UploadLinksTest(BaseTestCase):
def setUp(self):
@@ -46,6 +51,14 @@ class UploadLinksTest(BaseTestCase):
self._remove_upload_link(token)
+ @patch.object(UploadLinks, '_can_generate_shared_link')
+ def test_get_link_with_invalid_user_role_permission(self, mock_can_generate_shared_link):
+ self.login_as(self.user)
+ mock_can_generate_shared_link.return_value = False
+
+ resp = self.client.get(self.url)
+ self.assertEqual(403, resp.status_code)
+
def test_create_upload_link(self):
self.login_as(self.user)
@@ -61,6 +74,66 @@ class UploadLinksTest(BaseTestCase):
self._remove_upload_link(json_resp['token'])
+ @patch.object(UploadLinks, '_can_generate_shared_link')
+ def test_create_link_with_invalid_user_role_permission(self, mock_can_generate_shared_link):
+ self.login_as(self.user)
+ mock_can_generate_shared_link.return_value = False
+
+ resp = self.client.post(self.url, {'path': self.folder_path, 'repo_id': self.repo_id})
+ self.assertEqual(403, resp.status_code)
+
+ def test_create_link_with_rw_permission_folder(self):
+
+ if not LOCAL_PRO_DEV_ENV:
+ return
+
+ self.set_user_folder_rw_permission_to_admin()
+
+ # login with admin to create upload link in user's repo
+ self.login_as(self.admin)
+
+ data = {'path': self.folder_path, 'repo_id': self.repo_id}
+ resp = self.client.post(self.url, data)
+ self.assertEqual(200, resp.status_code)
+
+ def test_create_link_with_rw_permission_folder_in_group(self):
+
+ self.share_repo_to_group_with_rw_permission()
+ self.add_admin_to_group()
+
+ # login with admin to create upload link in user's repo
+ self.login_as(self.admin)
+
+ data = {'path': self.folder_path, 'repo_id': self.repo_id}
+ resp = self.client.post(self.url, data)
+ self.assertEqual(200, resp.status_code)
+
+ def test_can_not_create_link_with_r_permission_folder(self):
+
+ if not LOCAL_PRO_DEV_ENV:
+ return
+
+ self.set_user_folder_r_permission_to_admin()
+
+ # login with admin to create upload link in user's repo
+ self.login_as(self.admin)
+
+ data = {'path': self.folder_path, 'repo_id': self.repo_id}
+ resp = self.client.post(self.url, data)
+ self.assertEqual(403, resp.status_code)
+
+ def test_can_not_create_link_with_r_permission_folder_in_group(self):
+
+ self.share_repo_to_group_with_r_permission()
+ self.add_admin_to_group()
+
+ # login with admin to create upload link in user's repo
+ self.login_as(self.admin)
+
+ data = {'path': self.folder_path, 'repo_id': self.repo_id}
+ resp = self.client.post(self.url, data)
+ self.assertEqual(403, resp.status_code)
+
def test_delete_upload_link(self):
self.login_as(self.user)
token = self._add_upload_link()
@@ -72,43 +145,21 @@ class UploadLinksTest(BaseTestCase):
json_resp = json.loads(resp.content)
assert json_resp['success'] is True
- # test permission
- def test_can_not_delete_link_if_not_owner(self):
- self.login_as(self.admin)
+ @patch.object(UploadLink, '_can_generate_shared_link')
+ def test_delete_link_with_invalid_user_role_permission(self, mock_can_generate_shared_link):
token = self._add_upload_link()
- url = reverse('api-v2.1-upload-link', args=[token])
- resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
- self.assertEqual(403, resp.status_code)
-
- @patch.object(UploadLinks, '_can_generate_shared_link')
- def test_can_not_get_and_create_link_with_invalid_permission(self, mock_can_generate_shared_link):
self.login_as(self.user)
mock_can_generate_shared_link.return_value = False
- resp = self.client.get(self.url)
- self.assertEqual(403, resp.status_code)
-
- resp = self.client.post(self.url)
- self.assertEqual(403, resp.status_code)
-
- self.logout()
-
- # login with another user to test repo permission
- self.login_as(self.admin)
- mock_can_generate_shared_link.return_value = True
-
- args = '?repo_id=%s' % self.repo_id
- resp = self.client.get(self.url + args)
- self.assertEqual(403, resp.status_code)
-
- data = {'path': self.folder_path, 'repo_id': self.repo_id}
- resp = self.client.post(self.url, data)
- self.assertEqual(403, resp.status_code)
-
- @patch.object(UploadLink, '_can_generate_shared_link')
- def test_can_not_delete_link_with_invalid_permission(self, mock_can_generate_shared_link):
- token = self._add_upload_link()
+ url = reverse('api-v2.1-upload-link', args=[token])
+ resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
+ self.assertEqual(403, resp.status_code)
+
+ def test_delete_link_if_not_owner(self):
+ self.login_as(self.admin)
+ token = self._add_upload_link()
+
url = reverse('api-v2.1-upload-link', args=[token])
resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
self.assertEqual(403, resp.status_code)
diff --git a/tests/seahub/share/views/test_ajax_get_download_link.py b/tests/seahub/share/views/test_ajax_get_download_link.py
deleted file mode 100644
index 2a8729fd42..0000000000
--- a/tests/seahub/share/views/test_ajax_get_download_link.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import json
-
-from django.core.urlresolvers import reverse
-
-from seahub.share.models import FileShare
-from seahub.test_utils import BaseTestCase
-
-
-class AjaxGetDownloadLinkTest(BaseTestCase):
- def setUp(self):
-
- self.url = reverse('ajax_get_download_link')
-
- self.user_repo_id = self.repo.id
- self.user_dir_path = self.folder
- self.user_file_path = self.file
-
- def test_can_generate_file_share_link(self):
- self.login_as(self.user)
-
- url = self.url
- data = {
- 'repo_id': self.user_repo_id,
- 'p': self.user_file_path,
- 'type': 'f',
- }
- resp = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- json_resp = json.loads(resp.content)
- assert '/f/' in json_resp['download_link']
-
- def test_can_generate_dir_share_link(self):
- self.login_as(self.user)
-
- url = self.url
- data = {
- 'repo_id': self.user_repo_id,
- 'p': self.user_dir_path,
- 'type': 'd',
- }
- resp = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- json_resp = json.loads(resp.content)
- assert '/d/' in json_resp['download_link']
-
- def test_can_get_file_share_link(self):
- fs = FileShare.objects.create_file_link(self.user.username,
- self.user_repo_id, self.user_file_path)
-
- self.login_as(self.user)
-
- args = '?repo_id=%s&p=%s&type=%s' % (self.user_repo_id, self.user_file_path, 'f')
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- json_resp = json.loads(resp.content)
- assert fs.token in json_resp['download_link']
-
- def test_can_get_dir_share_link(self):
- fs = FileShare.objects.create_dir_link(self.user.username,
- self.user_repo_id, self.user_dir_path)
-
- self.login_as(self.user)
-
- args = '?repo_id=%s&p=%s&type=%s' % (self.user_repo_id, self.user_dir_path, 'd')
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- json_resp = json.loads(resp.content)
- assert fs.token in json_resp['download_link']
-
- def test_can_not_get_if_not_login(self):
- args = '?repo_id=%s&p=%s&type=%s' % (self.user_repo_id, self.user_file_path, 'f')
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(401, resp.status_code)
-
- def test_invalid_args(self):
- self.login_as(self.user)
-
- # invalid type
- args = '?repo_id=%s&p=%s&type=%s' % (self.user_repo_id, self.user_file_path, 'invalid_type')
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(400, resp.status_code)
-
- # invalid repo_id
- args = '?invalid_repo_id=%s&p=%s&type=%s' % (self.user_repo_id, self.user_file_path, 'f')
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(400, resp.status_code)
-
- # invalid path
- args = '?repo_id=%s&invalid_path=%s&type=%s' % (self.user_repo_id, self.user_file_path, 'f')
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(400, resp.status_code)
-
- def test_invalid_recourse(self):
- self.login_as(self.user)
-
- # invalid repo_id
- args = '?repo_id=%s&p=%s&type=%s' % ('invalid repo id', self.user_file_path, 'f')
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(500, resp.status_code)
-
- # invalid repo_id
- args = '?repo_id=%s&p=%s&type=%s' % (self.user_repo_id[:30] + '123456', self.user_file_path, 'f')
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(404, resp.status_code)
-
- # invalid path
- args = '?repo_id=%s&p=%s&type=%s' % (self.user_repo_id, 'invalid path', 'f')
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(404, resp.status_code)
-
- def test_invalid_permission(self):
- self.login_as(self.admin)
-
- args = '?repo_id=%s&p=%s&type=%s' % (self.user_repo_id, self.user_file_path, 'f')
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(403, resp.status_code)
diff --git a/tests/seahub/share/views/test_ajax_get_upload_link.py b/tests/seahub/share/views/test_ajax_get_upload_link.py
deleted file mode 100644
index 12f0775509..0000000000
--- a/tests/seahub/share/views/test_ajax_get_upload_link.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import json
-
-from django.core.urlresolvers import reverse
-
-from seahub.share.models import UploadLinkShare
-from seahub.test_utils import BaseTestCase
-
-
-class AjaxGetUploadLinkTest(BaseTestCase):
- def setUp(self):
- self.url = reverse('ajax_get_upload_link')
-
- self.user_repo_id = self.repo.id
- self.user_dir_path = self.folder
- self.user_file_path = self.file
-
- def test_can_generate(self):
- self.login_as(self.user)
-
- url = self.url
- data = {
- 'repo_id': self.user_repo_id,
- 'p': self.user_dir_path,
- }
- resp = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- json_resp = json.loads(resp.content)
- assert '/u/d/' in json_resp['upload_link']
-
- def test_can_get(self):
- upload_link = UploadLinkShare.objects.create_upload_link_share(
- self.user.username, self.user_repo_id, self.user_dir_path)
-
- self.login_as(self.user)
-
- args = '?repo_id=%s&p=%s' % (self.user_repo_id, self.user_dir_path)
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- json_resp = json.loads(resp.content)
- assert upload_link.token in json_resp['upload_link']
-
- def test_unlogin_user(self):
- args = '?repo_id=%s&p=%s' % (self.user_repo_id, self.user_dir_path)
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(401, resp.status_code)
-
- def test_invalid_args(self):
- self.login_as(self.user)
-
- # invalid repo_id
- args = '?invalid_repo_id=%s&p=%s' % (self.user_repo_id, self.user_dir_path)
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(400, resp.status_code)
-
- # invalid path
- args = '?repo_id=%s&invalid_path=%s' % (self.user_repo_id, self.user_dir_path)
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(400, resp.status_code)
-
- def test_invalid_recourse(self):
- self.login_as(self.user)
-
- # invalid repo_id
- args = '?repo_id=%s&p=%s' % ('invalid repo id', self.user_dir_path)
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(500, resp.status_code)
-
- # invalid repo_id
- args = '?repo_id=%s&p=%s' % (self.user_repo_id[:30] + '123456', self.user_dir_path)
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(404, resp.status_code)
-
- # invalid path
- args = '?repo_id=%s&p=%s' % (self.user_repo_id, 'invalid path')
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(404, resp.status_code)
-
- def test_invalid_permission(self):
- self.login_as(self.admin)
-
- args = '?repo_id=%s&p=%s' % (self.user_repo_id, self.user_dir_path)
- url = self.url + args
- resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(403, resp.status_code)
diff --git a/tests/seahub/share/views/test_list_priv_shared_folders.py b/tests/seahub/share/views/test_list_priv_shared_folders.py
deleted file mode 100644
index dc563bb57f..0000000000
--- a/tests/seahub/share/views/test_list_priv_shared_folders.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-
-from django.core.urlresolvers import reverse
-
-from seaserv import seafile_api
-
-from seahub.test_utils import BaseTestCase
-class ListPrivSharedFoldersTest(BaseTestCase):
- def tearDown(self):
- self.remove_repo()
-
- def test_can_list_priv_shared_folders(self):
- repo_id = self.repo.id
- username = self.user.username
-
- parent_dir = '/'
- dirname = 'test-folder'
- full_dir_path = os.path.join(parent_dir, dirname)
-
- # create folder
- self.create_folder(repo_id=repo_id,
- parent_dir=parent_dir,
- dirname=dirname,
- username=username)
-
- sub_repo_id = seafile_api.create_virtual_repo(repo_id, full_dir_path, dirname, dirname, username)
- seafile_api.share_repo(sub_repo_id, username, self.admin.username, 'rw')
-
- self.login_as(self.user)
- resp = self.client.get(reverse('list_priv_shared_folders'))
- self.assertEqual(200, resp.status_code)
- href = reverse("view_common_lib_dir", args=[repo_id, full_dir_path.strip('/')])
- self.assertRegexpMatches(resp.content, href)
diff --git a/tests/seahub/share/views/test_shared_links.py b/tests/seahub/share/views/test_shared_links.py
deleted file mode 100644
index e9e77a4dc5..0000000000
--- a/tests/seahub/share/views/test_shared_links.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-
-from django.test import TestCase
-from django.core.urlresolvers import reverse
-import requests
-
-from seahub.share.models import FileShare
-from seahub.test_utils import Fixtures, BaseTestCase
-
-class ListSharedLinksTest(BaseTestCase):
- def setUp(self):
- share_file_info = {
- 'username': 'test@test.com',
- 'repo_id': self.repo.id,
- 'path': self.file,
- 'password': None,
- 'expire_date': None,
- }
- self.fs = FileShare.objects.create_file_link(**share_file_info)
-
- def tearDown(self):
- self.remove_repo()
-
- def test_can_render(self):
- self.login_as(self.user)
-
- resp = self.client.get(reverse('list_shared_links'))
- self.assertEqual(200, resp.status_code)
- self.assertTemplateUsed(resp, 'share/links.html')
-
- def test_can_render_when_parent_dir_of_link_is_removed(self):
- """Issue https://github.com/haiwen/seafile/issues/1283
- """
- # create a file in a folder
- self.create_file(repo_id=self.repo.id,
- parent_dir=self.folder,
- filename='file.txt',
- username=self.user.username)
- # share that file
- share_file_info = {
- 'username': self.user.username,
- 'repo_id': self.repo.id,
- 'path': os.path.join(self.folder, 'file.txt'),
- 'password': None,
- 'expire_date': None,
- }
- fs = FileShare.objects.create_file_link(**share_file_info)
-
- self.login_as(self.user)
-
- resp = self.client.get(reverse('list_shared_links'))
- self.assertEqual(200, resp.status_code)
-
- # then delete parent folder, see whether it raises error
- self.remove_folder()
- resp = self.client.get(reverse('list_shared_links'))
- self.assertEqual(200, resp.status_code)