s3_website_monadic 0.0.37 → 0.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/bin/s3_website_monadic +1 -0
 - data/build.sbt +0 -4
 - data/lib/s3_website/version.rb +1 -1
 - data/src/main/scala/s3/website/CloudFront.scala +1 -1
 - data/src/main/scala/s3/website/Diff.scala +30 -33
 - data/src/main/scala/s3/website/Logger.scala +5 -3
 - data/src/main/scala/s3/website/Push.scala +3 -3
 - data/src/main/scala/s3/website/S3.scala +56 -55
 - data/src/main/scala/s3/website/model/Site.scala +1 -1
 - data/src/main/scala/s3/website/model/push.scala +35 -22
 - data/src/main/scala/s3/website/package.scala +2 -0
 - data/src/test/scala/s3/website/S3WebsiteSpec.scala +10 -17
 - metadata +2 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: df94a2c8b44f12db2fc393cd4de9126c68ba3f17
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: d9cd16f493ca0e4d861a7ed2b6b176cfdf58651e
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 377416d509c1df1ff449da81f87888aaf86cfec2423ddb615ac974b2c2383eef32ae1c6aee373b4ae177656fa4aebc052e95e8965131e07387c138143ffbf2b8
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: a89614a07d46403aaf97e29b693677ee2f704fece8836721f822fa17b728465e8c8b59e2d168f8b108e997ccc6d84708d839e8db9cdcbdc924b27418e2b055c4
         
     | 
    
        data/bin/s3_website_monadic
    CHANGED
    
    | 
         @@ -119,6 +119,7 @@ def autoinstall_java_or_print_help_and_exit(logger) 
     | 
|
| 
       119 
119 
     | 
    
         | 
| 
       120 
120 
     | 
    
         
             
              def print_manual_method_and_exit
         
     | 
| 
       121 
121 
     | 
    
         
             
                @logger.info_msg 'Go to http://java.com, install Java and then try again.'
         
     | 
| 
      
 122 
     | 
    
         
            +
                @logger.info_msg "(If you cannot or do not want to install Java, you can use latest 1.x version of this gem, which requires only Ruby. For more info, see https://github.com/laurilehmijoki/s3_website/tree/1.x)"
         
     | 
| 
       122 
123 
     | 
    
         
             
                exit 1
         
     | 
| 
       123 
124 
     | 
    
         
             
              end
         
     | 
| 
       124 
125 
     | 
    
         | 
    
        data/build.sbt
    CHANGED
    
    | 
         @@ -26,10 +26,6 @@ libraryDependencies += "org.apache.tika" % "tika-core" % "1.4" 
     | 
|
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
            libraryDependencies += "com.lexicalscope.jewelcli" % "jewelcli" % "0.8.9"
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
            libraryDependencies += "joda-time" % "joda-time" % "2.3"
         
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
            libraryDependencies += "org.joda" % "joda-convert" % "1.2"
         
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
29 
     | 
    
         
             
            libraryDependencies += "org.specs2" %% "specs2" % "2.3.11" % "test"
         
     | 
| 
       34 
30 
     | 
    
         | 
| 
       35 
31 
     | 
    
         
             
            resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
         
     | 
    
        data/lib/s3_website/version.rb
    CHANGED
    
    
| 
         @@ -117,7 +117,7 @@ object CloudFront { 
     | 
|
| 
       117 
117 
     | 
    
         | 
| 
       118 
118 
     | 
    
         | 
| 
       119 
119 
     | 
    
         
             
              def needsInvalidation: PartialFunction[PushSuccessReport, Boolean] = {
         
     | 
| 
       120 
     | 
    
         
            -
                case SuccessfulUpload 
     | 
| 
      
 120 
     | 
    
         
            +
                case succ: SuccessfulUpload => succ.details.fold(_.uploadType, _.uploadType) == FileUpdate
         
     | 
| 
       121 
121 
     | 
    
         
             
                case SuccessfulDelete(_) => true
         
     | 
| 
       122 
122 
     | 
    
         
             
                case _ => false
         
     | 
| 
       123 
123 
     | 
    
         
             
              }
         
     | 
| 
         @@ -19,7 +19,7 @@ case class Diff( 
     | 
|
| 
       19 
19 
     | 
    
         | 
| 
       20 
20 
     | 
    
         
             
            object Diff {
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
       22 
     | 
    
         
            -
              type UploadBatch = Future[Either[ErrorReport, Seq[ 
     | 
| 
      
 22 
     | 
    
         
            +
              type UploadBatch = Future[Either[ErrorReport, Seq[LocalFile]]]
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
       24 
24 
     | 
    
         
             
              def resolveDiff(s3FilesFuture: Future[Either[ErrorReport, Seq[S3File]]])
         
     | 
| 
       25 
25 
     | 
    
         
             
                             (implicit site: Site, logger: Logger, executor: ExecutionContextExecutor): Either[ErrorReport, Diff] =
         
     | 
| 
         @@ -28,26 +28,25 @@ object Diff { 
     | 
|
| 
       28 
28 
     | 
    
         | 
| 
       29 
29 
     | 
    
         
             
              private def resolveDiffAgainstGetBucketResponse(s3FilesFuture: Future[Either[ErrorReport, Seq[S3File]]])
         
     | 
| 
       30 
30 
     | 
    
         
             
                                                             (implicit site: Site, logger: Logger, executor: ExecutionContextExecutor): Either[ErrorReport, Diff] = {
         
     | 
| 
       31 
     | 
    
         
            -
                val  
     | 
| 
      
 31 
     | 
    
         
            +
                val diffAgainstS3 = s3FilesFuture.map { errorOrS3Files =>
         
     | 
| 
       32 
32 
     | 
    
         
             
                  errorOrS3Files.right.flatMap { s3Files =>
         
     | 
| 
       33 
33 
     | 
    
         
             
                    Try {
         
     | 
| 
       34 
34 
     | 
    
         
             
                      val s3KeyIndex = s3Files.map(_.s3Key).toSet
         
     | 
| 
       35 
35 
     | 
    
         
             
                      val s3Md5Index = s3Files.map(_.md5).toSet
         
     | 
| 
       36 
36 
     | 
    
         
             
                      val siteFiles = Files.listSiteFiles
         
     | 
| 
       37 
     | 
    
         
            -
                      val  
     | 
| 
       38 
     | 
    
         
            -
                      val  
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
                         
     | 
| 
       41 
     | 
    
         
            -
                       
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
                          .filterNot(localFile => s3Md5Index contains localFile.md5)
         
     | 
| 
      
 37 
     | 
    
         
            +
                      val existsOnS3 = (f: File) => s3KeyIndex contains site.resolveS3Key(f)
         
     | 
| 
      
 38 
     | 
    
         
            +
                      val isChangedOnS3 = (localFile: LocalFile) => !(s3Md5Index contains localFile.md5.get)
         
     | 
| 
      
 39 
     | 
    
         
            +
                      val newFiles = siteFiles collect {
         
     | 
| 
      
 40 
     | 
    
         
            +
                        case file if !existsOnS3(file) => LocalFile(file, NewFile)
         
     | 
| 
      
 41 
     | 
    
         
            +
                      }
         
     | 
| 
      
 42 
     | 
    
         
            +
                      val changedFiles = siteFiles collect {
         
     | 
| 
      
 43 
     | 
    
         
            +
                        case file if existsOnS3(file) => LocalFile(file, FileUpdate)
         
     | 
| 
      
 44 
     | 
    
         
            +
                      } filter isChangedOnS3
         
     | 
| 
       46 
45 
     | 
    
         
             
                      val unchangedFiles = {
         
     | 
| 
       47 
46 
     | 
    
         
             
                        val newOrChangedFiles = (changedFiles ++ newFiles).map(_.originalFile).toSet
         
     | 
| 
       48 
47 
     | 
    
         
             
                        siteFiles.filterNot(f => newOrChangedFiles contains f)
         
     | 
| 
       49 
48 
     | 
    
         
             
                      }
         
     | 
| 
       50 
     | 
    
         
            -
                      val allFiles: Seq[Either[DbRecord,  
     | 
| 
      
 49 
     | 
    
         
            +
                      val allFiles: Seq[Either[DbRecord, LocalFile]] = unchangedFiles.map {
         
     | 
| 
       51 
50 
     | 
    
         
             
                        f => Left(DbRecord(f))
         
     | 
| 
       52 
51 
     | 
    
         
             
                      } ++ (changedFiles ++ newFiles).map {
         
     | 
| 
       53 
52 
     | 
    
         
             
                        Right(_)
         
     | 
| 
         @@ -60,8 +59,8 @@ object Diff { 
     | 
|
| 
       60 
59 
     | 
    
         
             
                    }
         
     | 
| 
       61 
60 
     | 
    
         
             
                  }
         
     | 
| 
       62 
61 
     | 
    
         
             
                }
         
     | 
| 
       63 
     | 
    
         
            -
                def collectResult[B](pf: PartialFunction[Either[DbRecord,  
     | 
| 
       64 
     | 
    
         
            -
                   
     | 
| 
      
 62 
     | 
    
         
            +
                def collectResult[B](pf: PartialFunction[Either[DbRecord, LocalFile],B]) =
         
     | 
| 
      
 63 
     | 
    
         
            +
                  diffAgainstS3.map { errorOrDiffSource =>
         
     | 
| 
       65 
64 
     | 
    
         
             
                    errorOrDiffSource.right map (_ collect pf)
         
     | 
| 
       66 
65 
     | 
    
         
             
                  }
         
     | 
| 
       67 
66 
     | 
    
         
             
                val unchanged = collectResult {
         
     | 
| 
         @@ -78,16 +77,14 @@ object Diff { 
     | 
|
| 
       78 
77 
     | 
    
         
             
                val localKeys = for {
         
     | 
| 
       79 
78 
     | 
    
         
             
                  errorOrUnchanged <- diff.unchanged
         
     | 
| 
       80 
79 
     | 
    
         
             
                  errorsOrChanges  <- Future.sequence(diff.uploads)
         
     | 
| 
       81 
     | 
    
         
            -
                } yield 
     | 
| 
      
 80 
     | 
    
         
            +
                } yield
         
     | 
| 
       82 
81 
     | 
    
         
             
                  errorsOrChanges.foldLeft(errorOrUnchanged: Either[ErrorReport, Seq[S3Key]]) { (memo, errorOrChanges) =>
         
     | 
| 
       83 
82 
     | 
    
         
             
                    for {
         
     | 
| 
       84 
83 
     | 
    
         
             
                      mem <- memo.right
         
     | 
| 
       85 
84 
     | 
    
         
             
                      keysToDelete <- errorOrChanges.right
         
     | 
| 
       86 
     | 
    
         
            -
                    } yield  
     | 
| 
       87 
     | 
    
         
            -
                      mem ++ keysToDelete.map(_.s3Key)
         
     | 
| 
       88 
     | 
    
         
            -
                    }
         
     | 
| 
      
 85 
     | 
    
         
            +
                    } yield mem ++ keysToDelete.map(_.s3Key)
         
     | 
| 
       89 
86 
     | 
    
         
             
                  }
         
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
       91 
88 
     | 
    
         
             
                s3Files zip localKeys map { (s3Files: Either[ErrorReport, Seq[S3File]], errorOrLocalKeys: Either[ErrorReport, Seq[S3Key]]) =>
         
     | 
| 
       92 
89 
     | 
    
         
             
                  for {
         
     | 
| 
       93 
90 
     | 
    
         
             
                    localS3Keys <- errorOrLocalKeys.right
         
     | 
| 
         @@ -115,13 +112,13 @@ object Diff { 
     | 
|
| 
       115 
112 
     | 
    
         | 
| 
       116 
113 
     | 
    
         
             
                def resolveDiffAgainstLocalDb(s3FilesFuture: Future[Either[ErrorReport, Seq[S3File]]])
         
     | 
| 
       117 
114 
     | 
    
         
             
                                             (implicit site: Site, logger: Logger, executor: ExecutionContextExecutor): Either[ErrorReport, Diff] = {
         
     | 
| 
       118 
     | 
    
         
            -
                  val localDiff: Either[ErrorReport, Seq[Either[DbRecord,  
     | 
| 
      
 115 
     | 
    
         
            +
                  val localDiff: Either[ErrorReport, Seq[Either[DbRecord, LocalFile]]] =
         
     | 
| 
       119 
116 
     | 
    
         
             
                    (for {
         
     | 
| 
       120 
117 
     | 
    
         
             
                      dbFile <- getOrCreateDbFile
         
     | 
| 
       121 
118 
     | 
    
         
             
                      databaseIndices <- loadDbFromFile(dbFile)
         
     | 
| 
       122 
119 
     | 
    
         
             
                    } yield {
         
     | 
| 
       123 
120 
     | 
    
         
             
                      val siteFiles = Files.listSiteFiles
         
     | 
| 
       124 
     | 
    
         
            -
                      val recordsOrChangedFiles = siteFiles.foldLeft(Seq(): Seq[Either[DbRecord,  
     | 
| 
      
 121 
     | 
    
         
            +
                      val recordsOrChangedFiles = siteFiles.foldLeft(Seq(): Seq[Either[DbRecord, LocalFile]]) { (localFiles, file) =>
         
     | 
| 
       125 
122 
     | 
    
         
             
                        val truncatedKey = TruncatedDbRecord(file)
         
     | 
| 
       126 
123 
     | 
    
         
             
                        val fileIsUnchanged = databaseIndices.truncatedIndex contains truncatedKey
         
     | 
| 
       127 
124 
     | 
    
         
             
                        if (fileIsUnchanged)
         
     | 
| 
         @@ -130,7 +127,7 @@ object Diff { 
     | 
|
| 
       130 
127 
     | 
    
         
             
                          val uploadType =
         
     | 
| 
       131 
128 
     | 
    
         
             
                            if (databaseIndices.s3KeyIndex contains truncatedKey.s3Key) FileUpdate
         
     | 
| 
       132 
129 
     | 
    
         
             
                            else NewFile
         
     | 
| 
       133 
     | 
    
         
            -
                          localFiles :+ Right( 
     | 
| 
      
 130 
     | 
    
         
            +
                          localFiles :+ Right(LocalFile(file, uploadType))
         
     | 
| 
       134 
131 
     | 
    
         
             
                        }
         
     | 
| 
       135 
132 
     | 
    
         
             
                      }
         
     | 
| 
       136 
133 
     | 
    
         
             
                      logger.debug(s"Discovered ${siteFiles.length} files on the local site, of which ${recordsOrChangedFiles count (_.isRight)} are new or changed")
         
     | 
| 
         @@ -145,11 +142,11 @@ object Diff { 
     | 
|
| 
       145 
142 
     | 
    
         
             
                      case Left(f) => f
         
     | 
| 
       146 
143 
     | 
    
         
             
                    }
         
     | 
| 
       147 
144 
     | 
    
         | 
| 
       148 
     | 
    
         
            -
                    val  
     | 
| 
      
 145 
     | 
    
         
            +
                    val uploadsAccordingToLocalDiff = localDiffResult collect {
         
     | 
| 
       149 
146 
     | 
    
         
             
                      case Right(f) => f
         
     | 
| 
       150 
147 
     | 
    
         
             
                    }
         
     | 
| 
       151 
148 
     | 
    
         | 
| 
       152 
     | 
    
         
            -
                    val changesMissedByLocalDiff: Future[Either[ErrorReport, Seq[ 
     | 
| 
      
 149 
     | 
    
         
            +
                    val changesMissedByLocalDiff: Future[Either[ErrorReport, Seq[LocalFile]]] = s3FilesFuture.map { errorOrS3Files =>
         
     | 
| 
       153 
150 
     | 
    
         
             
                      for (s3Files <- errorOrS3Files.right) yield {
         
     | 
| 
       154 
151 
     | 
    
         
             
                        val remoteS3Keys = s3Files.map(_.s3Key).toSet
         
     | 
| 
       155 
152 
     | 
    
         
             
                        val localS3Keys = unchangedAccordingToLocalDiff.map(_.s3Key).toSet
         
     | 
| 
         @@ -157,11 +154,11 @@ object Diff { 
     | 
|
| 
       157 
154 
     | 
    
         
             
                        def isChangedOnS3(s3File: S3File) = (localS3Keys contains s3File.s3Key) && !(localMd5 contains s3File.md5)
         
     | 
| 
       158 
155 
     | 
    
         
             
                        val changedOnS3 = s3Files collect {
         
     | 
| 
       159 
156 
     | 
    
         
             
                          case s3File if isChangedOnS3(s3File) =>
         
     | 
| 
       160 
     | 
    
         
            -
                             
     | 
| 
      
 157 
     | 
    
         
            +
                            LocalFile(site resolveFile s3File, FileUpdate)
         
     | 
| 
       161 
158 
     | 
    
         
             
                        }
         
     | 
| 
       162 
159 
     | 
    
         
             
                        val missingFromS3 = localS3Keys collect {
         
     | 
| 
       163 
160 
     | 
    
         
             
                          case localS3Key if !(remoteS3Keys contains localS3Key) =>
         
     | 
| 
       164 
     | 
    
         
            -
                             
     | 
| 
      
 161 
     | 
    
         
            +
                            LocalFile(site resolveFile localS3Key, NewFile)
         
     | 
| 
       165 
162 
     | 
    
         | 
| 
       166 
163 
     | 
    
         
             
                        }
         
     | 
| 
       167 
164 
     | 
    
         
             
                        changedOnS3 ++ missingFromS3
         
     | 
| 
         @@ -180,14 +177,14 @@ object Diff { 
     | 
|
| 
       180 
177 
     | 
    
         
             
                    val unchangedFilesFinal = errorOrDiffAgainstS3 map {
         
     | 
| 
       181 
178 
     | 
    
         
             
                      _ fold (
         
     | 
| 
       182 
179 
     | 
    
         
             
                        (error: ErrorReport) => Left(error),
         
     | 
| 
       183 
     | 
    
         
            -
                        (syncResult: (Seq[DbRecord], Seq[ 
     | 
| 
      
 180 
     | 
    
         
            +
                        (syncResult: (Seq[DbRecord], Seq[LocalFile])) => Right(syncResult._1)
         
     | 
| 
       184 
181 
     | 
    
         
             
                      )
         
     | 
| 
       185 
182 
     | 
    
         
             
                    }
         
     | 
| 
       186 
183 
     | 
    
         | 
| 
       187 
184 
     | 
    
         
             
                    val changedAccordingToS3Diff = errorOrDiffAgainstS3.map {
         
     | 
| 
       188 
185 
     | 
    
         
             
                      _ fold (
         
     | 
| 
       189 
186 
     | 
    
         
             
                        (error: ErrorReport) => Left(error),
         
     | 
| 
       190 
     | 
    
         
            -
                        (syncResult: (Seq[DbRecord], Seq[ 
     | 
| 
      
 187 
     | 
    
         
            +
                        (syncResult: (Seq[DbRecord], Seq[LocalFile])) => Right(syncResult._2)
         
     | 
| 
       191 
188 
     | 
    
         
             
                      )
         
     | 
| 
       192 
189 
     | 
    
         
             
                    }
         
     | 
| 
       193 
190 
     | 
    
         
             
                    val persistenceError: Future[Either[ErrorReport, _]] = for {
         
     | 
| 
         @@ -198,13 +195,13 @@ object Diff { 
     | 
|
| 
       198 
195 
     | 
    
         
             
                        records1 <- unchanged.right
         
     | 
| 
       199 
196 
     | 
    
         
             
                        records2 <- changedAccordingToS3.right
         
     | 
| 
       200 
197 
     | 
    
         
             
                      } yield
         
     | 
| 
       201 
     | 
    
         
            -
                        persist(records1.map(Left(_)) ++ records2.map(Right(_)) ++  
     | 
| 
      
 198 
     | 
    
         
            +
                        persist(records1.map(Left(_)) ++ records2.map(Right(_)) ++ uploadsAccordingToLocalDiff.map(Right(_))) match {
         
     | 
| 
       202 
199 
     | 
    
         
             
                          case Success(_)   => Unit
         
     | 
| 
       203 
200 
     | 
    
         
             
                          case Failure(err) => ErrorReport(err)
         
     | 
| 
       204 
201 
     | 
    
         
             
                        }
         
     | 
| 
       205 
202 
     | 
    
         
             
                    Diff(
         
     | 
| 
       206 
203 
     | 
    
         
             
                      unchangedFilesFinal map (_.right.map(_ map (_.s3Key))),
         
     | 
| 
       207 
     | 
    
         
            -
                      uploads = Future(Right( 
     | 
| 
      
 204 
     | 
    
         
            +
                      uploads = Future(Right(uploadsAccordingToLocalDiff)) :: changedAccordingToS3Diff :: Nil,
         
     | 
| 
       208 
205 
     | 
    
         
             
                      persistenceError = persistenceError map (_.left.toOption)
         
     | 
| 
       209 
206 
     | 
    
         
             
                    )
         
     | 
| 
       210 
207 
     | 
    
         
             
                  }
         
     | 
| 
         @@ -244,13 +241,13 @@ object Diff { 
     | 
|
| 
       244 
241 
     | 
    
         
             
                    )
         
     | 
| 
       245 
242 
     | 
    
         
             
                  }
         
     | 
| 
       246 
243 
     | 
    
         | 
| 
       247 
     | 
    
         
            -
                def persist(recordsOrChangedFiles: Seq[Either[DbRecord,  
     | 
| 
      
 244 
     | 
    
         
            +
                def persist(recordsOrChangedFiles: Seq[Either[DbRecord, LocalFile]])(implicit site: Site, logger: Logger): Try[Seq[Either[DbRecord, LocalFile]]] =
         
     | 
| 
       248 
245 
     | 
    
         
             
                  getOrCreateDbFile flatMap { dbFile =>
         
     | 
| 
       249 
246 
     | 
    
         
             
                    Try {
         
     | 
| 
       250 
247 
     | 
    
         
             
                      val dbFileContents = recordsOrChangedFiles.map { recordOrChangedFile =>
         
     | 
| 
       251 
248 
     | 
    
         
             
                        val record: DbRecord = recordOrChangedFile fold(
         
     | 
| 
       252 
249 
     | 
    
         
             
                          record => record,
         
     | 
| 
       253 
     | 
    
         
            -
                          changedFile => DbRecord(changedFile.s3Key, changedFile.originalFile.length, changedFile.originalFile.lastModified, changedFile.md5)
         
     | 
| 
      
 250 
     | 
    
         
            +
                          changedFile => DbRecord(changedFile.s3Key, changedFile.originalFile.length, changedFile.originalFile.lastModified, changedFile.md5.get)
         
     | 
| 
       254 
251 
     | 
    
         
             
                          )
         
     | 
| 
       255 
252 
     | 
    
         
             
                        record.s3Key :: record.fileLength :: record.fileModified :: record.uploadFileMd5 :: Nil mkString "|"
         
     | 
| 
       256 
253 
     | 
    
         
             
                      } mkString "\n"
         
     | 
| 
         @@ -278,6 +275,6 @@ object Diff { 
     | 
|
| 
       278 
275 
     | 
    
         | 
| 
       279 
276 
     | 
    
         
             
              object DbRecord {
         
     | 
| 
       280 
277 
     | 
    
         
             
                def apply(original: File)(implicit site: Site): DbRecord =
         
     | 
| 
       281 
     | 
    
         
            -
                  DbRecord(site resolveS3Key original, original.length, original.lastModified,  
     | 
| 
      
 278 
     | 
    
         
            +
                  DbRecord(site resolveS3Key original, original.length, original.lastModified, LocalFile.md5(original).get)
         
     | 
| 
       282 
279 
     | 
    
         
             
              }
         
     | 
| 
       283 
280 
     | 
    
         
             
            }
         
     | 
| 
         @@ -1,6 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            package s3.website
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
      
 3 
     | 
    
         
            +
            import scala.util.Try
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            class Logger(val verboseOutput: Boolean) {
         
     | 
| 
       4 
6 
     | 
    
         
             
              def debug(msg: String) = if (verboseOutput) log(Debug, msg)
         
     | 
| 
       5 
7 
     | 
    
         
             
              def info(msg: String) = log(Info, msg)
         
     | 
| 
       6 
8 
     | 
    
         
             
              def fail(msg: String) = log(Failure, msg)
         
     | 
| 
         @@ -10,9 +12,9 @@ class Logger(val verboseOutput: Boolean, logMessage: (String) => Unit = println) 
     | 
|
| 
       10 
12 
     | 
    
         | 
| 
       11 
13 
     | 
    
         
             
              def pending(msg: String) = log(Wait, msg)
         
     | 
| 
       12 
14 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
              private def log(logType: LogType, msgRaw: String) {
         
     | 
| 
      
 15 
     | 
    
         
            +
              private def log(logType: LogType, msgRaw: String): Try[Unit] = {
         
     | 
| 
       14 
16 
     | 
    
         
             
                val msg = msgRaw.replaceAll("\\n", "\n       ") // Indent new lines, so that they arrange nicely with other log lines
         
     | 
| 
       15 
     | 
    
         
            -
                 
     | 
| 
      
 17 
     | 
    
         
            +
                Try(println(s"[$logType] $msg"))
         
     | 
| 
       16 
18 
     | 
    
         
             
              }
         
     | 
| 
       17 
19 
     | 
    
         | 
| 
       18 
20 
     | 
    
         
             
              sealed trait LogType {
         
     | 
| 
         @@ -191,7 +191,7 @@ object Push { 
     | 
|
| 
       191 
191 
     | 
    
         
             
                      (failureReport: PushFailureReport) => counts.copy(failures = counts.failures + 1),
         
     | 
| 
       192 
192 
     | 
    
         
             
                      (successReport: PushSuccessReport) =>
         
     | 
| 
       193 
193 
     | 
    
         
             
                        successReport match {
         
     | 
| 
       194 
     | 
    
         
            -
                          case succ: SuccessfulUpload => succ. 
     | 
| 
      
 194 
     | 
    
         
            +
                          case succ: SuccessfulUpload => succ.details.fold(_.uploadType, _.uploadType) match {
         
     | 
| 
       195 
195 
     | 
    
         
             
                            case NewFile      => counts.copy(newFiles = counts.newFiles + 1).addTransferStats(succ) // TODO nasty repetition here
         
     | 
| 
       196 
196 
     | 
    
         
             
                            case FileUpdate   => counts.copy(updates = counts.updates + 1).addTransferStats(succ)
         
     | 
| 
       197 
197 
     | 
    
         
             
                            case RedirectFile => counts.copy(redirects = counts.redirects + 1).addTransferStats(succ)
         
     | 
| 
         @@ -228,14 +228,14 @@ object Push { 
     | 
|
| 
       228 
228 
     | 
    
         
             
                                     redirects: Int = 0, 
         
     | 
| 
       229 
229 
     | 
    
         
             
                                     deletes: Int = 0,
         
     | 
| 
       230 
230 
     | 
    
         
             
                                     uploadedBytes: Long = 0,
         
     | 
| 
       231 
     | 
    
         
            -
                                     uploadDurations: Seq[ 
     | 
| 
      
 231 
     | 
    
         
            +
                                     uploadDurations: Seq[UploadDuration] = Nil
         
     | 
| 
       232 
232 
     | 
    
         
             
                                     ) {
         
     | 
| 
       233 
233 
     | 
    
         
             
                val thereWasSomethingToPush = updates + newFiles + redirects + deletes > 0
         
     | 
| 
       234 
234 
     | 
    
         | 
| 
       235 
235 
     | 
    
         
             
                def addTransferStats(successfulUpload: SuccessfulUpload): PushCounts =
         
     | 
| 
       236 
236 
     | 
    
         
             
                  copy(
         
     | 
| 
       237 
237 
     | 
    
         
             
                    uploadedBytes = uploadedBytes + (successfulUpload.uploadSize getOrElse 0L),
         
     | 
| 
       238 
     | 
    
         
            -
                    uploadDurations = uploadDurations ++ successfulUpload.uploadDuration
         
     | 
| 
      
 238 
     | 
    
         
            +
                    uploadDurations = uploadDurations ++ successfulUpload.details.fold(_.uploadDuration, _ => None)
         
     | 
| 
       239 
239 
     | 
    
         
             
                  )
         
     | 
| 
       240 
240 
     | 
    
         
             
              }
         
     | 
| 
       241 
241 
     | 
    
         | 
| 
         @@ -2,26 +2,17 @@ package s3.website 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            import s3.website.model._
         
     | 
| 
       4 
4 
     | 
    
         
             
            import com.amazonaws.services.s3.{AmazonS3, AmazonS3Client}
         
     | 
| 
       5 
     | 
    
         
            -
            import com.amazonaws.auth.BasicAWSCredentials
         
     | 
| 
       6 
5 
     | 
    
         
             
            import com.amazonaws.services.s3.model._
         
     | 
| 
       7 
6 
     | 
    
         
             
            import scala.collection.JavaConversions._
         
     | 
| 
       8 
7 
     | 
    
         
             
            import scala.concurrent.{ExecutionContextExecutor, Future}
         
     | 
| 
       9 
8 
     | 
    
         
             
            import com.amazonaws.services.s3.model.StorageClass.ReducedRedundancy
         
     | 
| 
       10 
     | 
    
         
            -
            import s3.website.S3.SuccessfulUpload
         
     | 
| 
       11 
     | 
    
         
            -
            import s3.website.S3.SuccessfulDelete
         
     | 
| 
       12 
     | 
    
         
            -
            import s3.website.S3.FailedUpload
         
     | 
| 
       13 
     | 
    
         
            -
            import scala.Some
         
     | 
| 
       14 
     | 
    
         
            -
            import s3.website.S3.FailedDelete
         
     | 
| 
       15 
     | 
    
         
            -
            import s3.website.S3.S3Setting
         
     | 
| 
       16 
9 
     | 
    
         
             
            import s3.website.ByteHelper.humanReadableByteCount
         
     | 
| 
       17 
     | 
    
         
            -
            import org.joda.time.{Seconds, Duration, Interval}
         
     | 
| 
       18 
     | 
    
         
            -
            import scala.concurrent.duration.TimeUnit
         
     | 
| 
       19 
     | 
    
         
            -
            import java.util.concurrent.TimeUnit
         
     | 
| 
       20 
10 
     | 
    
         
             
            import scala.concurrent.duration.TimeUnit
         
     | 
| 
       21 
11 
     | 
    
         
             
            import java.util.concurrent.TimeUnit.SECONDS
         
     | 
| 
       22 
12 
     | 
    
         
             
            import s3.website.S3.SuccessfulUpload.humanizeUploadSpeed
         
     | 
| 
       23 
13 
     | 
    
         
             
            import java.io.FileInputStream
         
     | 
| 
       24 
14 
     | 
    
         
             
            import s3.website.model.Config.awsCredentials
         
     | 
| 
      
 15 
     | 
    
         
            +
            import scala.util.Try
         
     | 
| 
       25 
16 
     | 
    
         | 
| 
       26 
17 
     | 
    
         
             
            object S3 {
         
     | 
| 
       27 
18 
     | 
    
         | 
| 
         @@ -29,19 +20,26 @@ object S3 { 
     | 
|
| 
       29 
20 
     | 
    
         
             
                        (implicit config: Config, s3Settings: S3Setting, pushMode: PushMode, executor: ExecutionContextExecutor, logger: Logger) =
         
     | 
| 
       30 
21 
     | 
    
         
             
                upload(Right(redirect))
         
     | 
| 
       31 
22 
     | 
    
         | 
| 
       32 
     | 
    
         
            -
              def uploadFile(localFile:  
     | 
| 
      
 23 
     | 
    
         
            +
              def uploadFile(localFile: LocalFile, a: Attempt = 1)
         
     | 
| 
       33 
24 
     | 
    
         
             
                                (implicit config: Config, s3Settings: S3Setting, pushMode: PushMode, executor: ExecutionContextExecutor, logger: Logger) =
         
     | 
| 
       34 
25 
     | 
    
         
             
                upload(Left(localFile))
         
     | 
| 
       35 
26 
     | 
    
         | 
| 
       36 
     | 
    
         
            -
              def upload(source: Either[ 
     | 
| 
      
 27 
     | 
    
         
            +
              def upload(source: Either[LocalFile, Redirect], a: Attempt = 1)
         
     | 
| 
       37 
28 
     | 
    
         
             
                        (implicit config: Config, s3Settings: S3Setting, pushMode: PushMode, executor: ExecutionContextExecutor, logger: Logger):
         
     | 
| 
       38 
29 
     | 
    
         
             
              Future[Either[FailedUpload, SuccessfulUpload]] =
         
     | 
| 
       39 
30 
     | 
    
         
             
                Future {
         
     | 
| 
       40 
     | 
    
         
            -
                  val putObjectRequest = toPutObjectRequest(source)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  val putObjectRequest = toPutObjectRequest(source).get
         
     | 
| 
       41 
32 
     | 
    
         
             
                  val uploadDuration =
         
     | 
| 
       42 
33 
     | 
    
         
             
                    if (pushMode.dryRun) None
         
     | 
| 
       43 
     | 
    
         
            -
                    else recordUploadDuration(putObjectRequest, s3Settings.s3Client(config) putObject putObjectRequest)
         
     | 
| 
       44 
     | 
    
         
            -
                  val report = SuccessfulUpload( 
     | 
| 
      
 34 
     | 
    
         
            +
                    else Some(recordUploadDuration(putObjectRequest, s3Settings.s3Client(config) putObject putObjectRequest))
         
     | 
| 
      
 35 
     | 
    
         
            +
                  val report = SuccessfulUpload(
         
     | 
| 
      
 36 
     | 
    
         
            +
                    source.fold(_.s3Key, _.s3Key),
         
     | 
| 
      
 37 
     | 
    
         
            +
                    source.fold(
         
     | 
| 
      
 38 
     | 
    
         
            +
                      localFile => Left(SuccessfulNewOrCreatedDetails(localFile.uploadType, localFile.uploadFile.get.length(), uploadDuration)),
         
     | 
| 
      
 39 
     | 
    
         
            +
                      redirect  => Right(SuccessfulRedirectDetails(redirect.uploadType, redirect.redirectTarget))
         
     | 
| 
      
 40 
     | 
    
         
            +
                    ),
         
     | 
| 
      
 41 
     | 
    
         
            +
                    putObjectRequest
         
     | 
| 
      
 42 
     | 
    
         
            +
                  )
         
     | 
| 
       45 
43 
     | 
    
         
             
                  logger.info(report)
         
     | 
| 
       46 
44 
     | 
    
         
             
                  Right(report)
         
     | 
| 
       47 
45 
     | 
    
         
             
                } recoverWith retry(a)(
         
     | 
| 
         @@ -62,25 +60,30 @@ object S3 { 
     | 
|
| 
       62 
60 
     | 
    
         
             
                  retryAction  = newAttempt => this.delete(s3Key, newAttempt)
         
     | 
| 
       63 
61 
     | 
    
         
             
                )
         
     | 
| 
       64 
62 
     | 
    
         | 
| 
       65 
     | 
    
         
            -
              def toPutObjectRequest(source: Either[ 
     | 
| 
      
 63 
     | 
    
         
            +
              def toPutObjectRequest(source: Either[LocalFile, Redirect])(implicit config: Config): Try[PutObjectRequest] =
         
     | 
| 
       66 
64 
     | 
    
         
             
                source.fold(
         
     | 
| 
       67 
     | 
    
         
            -
                  localFile => 
     | 
| 
       68 
     | 
    
         
            -
                     
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
                     
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       73 
     | 
    
         
            -
                      md. 
     | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
       77 
     | 
    
         
            -
             
     | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
      
 65 
     | 
    
         
            +
                  localFile =>
         
     | 
| 
      
 66 
     | 
    
         
            +
                    for {
         
     | 
| 
      
 67 
     | 
    
         
            +
                      uploadFile <- localFile.uploadFile
         
     | 
| 
      
 68 
     | 
    
         
            +
                      contentType <- localFile.contentType
         
     | 
| 
      
 69 
     | 
    
         
            +
                    } yield {
         
     | 
| 
      
 70 
     | 
    
         
            +
                      val md = new ObjectMetadata()
         
     | 
| 
      
 71 
     | 
    
         
            +
                      md setContentLength uploadFile.length
         
     | 
| 
      
 72 
     | 
    
         
            +
                      md setContentType contentType
         
     | 
| 
      
 73 
     | 
    
         
            +
                      localFile.encodingOnS3.map(_ => "gzip") foreach md.setContentEncoding
         
     | 
| 
      
 74 
     | 
    
         
            +
                      localFile.maxAge foreach { seconds =>
         
     | 
| 
      
 75 
     | 
    
         
            +
                        md.setCacheControl(
         
     | 
| 
      
 76 
     | 
    
         
            +
                          if (seconds == 0)
         
     | 
| 
      
 77 
     | 
    
         
            +
                            s"no-cache; max-age=$seconds"
         
     | 
| 
      
 78 
     | 
    
         
            +
                          else
         
     | 
| 
      
 79 
     | 
    
         
            +
                            s"max-age=$seconds"
         
     | 
| 
      
 80 
     | 
    
         
            +
                        )
         
     | 
| 
      
 81 
     | 
    
         
            +
                      }
         
     | 
| 
      
 82 
     | 
    
         
            +
                      val req = new PutObjectRequest(config.s3_bucket, localFile.s3Key, new FileInputStream(uploadFile), md)
         
     | 
| 
      
 83 
     | 
    
         
            +
                      config.s3_reduced_redundancy.filter(_ == true) foreach (_ => req setStorageClass ReducedRedundancy)
         
     | 
| 
      
 84 
     | 
    
         
            +
                      req
         
     | 
| 
       79 
85 
     | 
    
         
             
                    }
         
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
       81 
     | 
    
         
            -
                    config.s3_reduced_redundancy.filter(_ == true) foreach (_ => req setStorageClass ReducedRedundancy)
         
     | 
| 
       82 
     | 
    
         
            -
                    req
         
     | 
| 
       83 
     | 
    
         
            -
                  },
         
     | 
| 
      
 86 
     | 
    
         
            +
                  ,
         
     | 
| 
       84 
87 
     | 
    
         
             
                  redirect => {
         
     | 
| 
       85 
88 
     | 
    
         
             
                    val req = new PutObjectRequest(config.s3_bucket, redirect.s3Key, redirect.redirectTarget)
         
     | 
| 
       86 
89 
     | 
    
         
             
                    req.setMetadata({
         
     | 
| 
         @@ -93,17 +96,14 @@ object S3 { 
     | 
|
| 
       93 
96 
     | 
    
         
             
                      md.setCacheControl("max-age=0, no-cache")
         
     | 
| 
       94 
97 
     | 
    
         
             
                      md
         
     | 
| 
       95 
98 
     | 
    
         
             
                    })
         
     | 
| 
       96 
     | 
    
         
            -
                    req
         
     | 
| 
      
 99 
     | 
    
         
            +
                    Try(req)
         
     | 
| 
       97 
100 
     | 
    
         
             
                  }
         
     | 
| 
       98 
101 
     | 
    
         
             
                )
         
     | 
| 
       99 
102 
     | 
    
         | 
| 
       100 
     | 
    
         
            -
              def recordUploadDuration(putObjectRequest: PutObjectRequest, f: => Unit):  
     | 
| 
      
 103 
     | 
    
         
            +
              def recordUploadDuration(putObjectRequest: PutObjectRequest, f: => Unit): UploadDuration = {
         
     | 
| 
       101 
104 
     | 
    
         
             
                val start = System.currentTimeMillis()
         
     | 
| 
       102 
105 
     | 
    
         
             
                f
         
     | 
| 
       103 
     | 
    
         
            -
                 
     | 
| 
       104 
     | 
    
         
            -
                  Some(new Duration(start, System.currentTimeMillis))
         
     | 
| 
       105 
     | 
    
         
            -
                else
         
     | 
| 
       106 
     | 
    
         
            -
                  None // We are not interested in tracking durations of PUT requests that don't contain data. Redirect is an example of such request.
         
     | 
| 
      
 106 
     | 
    
         
            +
                System.currentTimeMillis - start
         
     | 
| 
       107 
107 
     | 
    
         
             
              }
         
     | 
| 
       108 
108 
     | 
    
         | 
| 
       109 
109 
     | 
    
         
             
              def awsS3Client(config: Config) = new AmazonS3Client(awsCredentials(config))
         
     | 
| 
         @@ -145,14 +145,18 @@ object S3 { 
     | 
|
| 
       145 
145 
     | 
    
         
             
                def s3Key: String
         
     | 
| 
       146 
146 
     | 
    
         
             
              }
         
     | 
| 
       147 
147 
     | 
    
         | 
| 
       148 
     | 
    
         
            -
              case class  
     | 
| 
      
 148 
     | 
    
         
            +
              case class SuccessfulRedirectDetails(uploadType: UploadType, redirectTarget: String)
         
     | 
| 
      
 149 
     | 
    
         
            +
              case class SuccessfulNewOrCreatedDetails(uploadType: UploadType, uploadSize: Long, uploadDuration: Option[Long])
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
              case class SuccessfulUpload(s3Key: S3Key,
         
     | 
| 
      
 152 
     | 
    
         
            +
                                          details: Either[SuccessfulNewOrCreatedDetails, SuccessfulRedirectDetails],
         
     | 
| 
      
 153 
     | 
    
         
            +
                                          putObjectRequest: PutObjectRequest)
         
     | 
| 
       149 
154 
     | 
    
         
             
                                         (implicit pushMode: PushMode, logger: Logger) extends PushSuccessReport {
         
     | 
| 
       150 
155 
     | 
    
         
             
                def reportMessage =
         
     | 
| 
       151 
     | 
    
         
            -
                   
     | 
| 
       152 
     | 
    
         
            -
                     
     | 
| 
       153 
     | 
    
         
            -
                     
     | 
| 
       154 
     | 
    
         
            -
             
     | 
| 
       155 
     | 
    
         
            -
                  }
         
     | 
| 
      
 156 
     | 
    
         
            +
                  details.fold(
         
     | 
| 
      
 157 
     | 
    
         
            +
                    newOrCreatedDetails => s"${newOrCreatedDetails.uploadType.pushAction} $s3Key ($reportDetails)",
         
     | 
| 
      
 158 
     | 
    
         
            +
                    redirectDetails     => s"${redirectDetails.uploadType.pushAction} $s3Key to ${redirectDetails.redirectTarget}"
         
     | 
| 
      
 159 
     | 
    
         
            +
                  )
         
     | 
| 
       156 
160 
     | 
    
         | 
| 
       157 
161 
     | 
    
         
             
                def reportDetails = {
         
     | 
| 
       158 
162 
     | 
    
         
             
                  val md = putObjectRequest.getMetadata
         
     | 
| 
         @@ -169,12 +173,9 @@ object S3 { 
     | 
|
| 
       169 
173 
     | 
    
         
             
                  }.mkString(" | ")
         
     | 
| 
       170 
174 
     | 
    
         
             
                }
         
     | 
| 
       171 
175 
     | 
    
         | 
| 
       172 
     | 
    
         
            -
                 
     | 
| 
       173 
     | 
    
         
            -
             
     | 
| 
       174 
     | 
    
         
            -
             
     | 
| 
       175 
     | 
    
         
            -
                  source.fold(
         
     | 
| 
       176 
     | 
    
         
            -
                    (localFile: LocalFileFromDisk) => Some(localFile.uploadFile.length()),
         
     | 
| 
       177 
     | 
    
         
            -
                    (redirect: Redirect)           => None
         
     | 
| 
      
 176 
     | 
    
         
            +
                lazy val uploadSize = details.fold(
         
     | 
| 
      
 177 
     | 
    
         
            +
                  newOrCreatedDetails => Some(newOrCreatedDetails.uploadSize),
         
     | 
| 
      
 178 
     | 
    
         
            +
                  redirectDetails     => None
         
     | 
| 
       178 
179 
     | 
    
         
             
                )
         
     | 
| 
       179 
180 
     | 
    
         | 
| 
       180 
181 
     | 
    
         
             
                lazy val uploadSizeForHumans: Option[String] = uploadSize filter (_ => logger.verboseOutput) map humanReadableByteCount
         
     | 
| 
         @@ -182,17 +183,17 @@ object S3 { 
     | 
|
| 
       182 
183 
     | 
    
         
             
                lazy val uploadSpeedForHumans: Option[String] =
         
     | 
| 
       183 
184 
     | 
    
         
             
                  (for {
         
     | 
| 
       184 
185 
     | 
    
         
             
                    dataSize <- uploadSize
         
     | 
| 
       185 
     | 
    
         
            -
                    duration <- uploadDuration
         
     | 
| 
      
 186 
     | 
    
         
            +
                    duration <- details.left.map(_.uploadDuration).left.toOption.flatten
         
     | 
| 
       186 
187 
     | 
    
         
             
                  } yield {
         
     | 
| 
       187 
188 
     | 
    
         
             
                    humanizeUploadSpeed(dataSize, duration)
         
     | 
| 
       188 
189 
     | 
    
         
             
                  }) flatMap identity filter (_ => logger.verboseOutput)
         
     | 
| 
       189 
190 
     | 
    
         
             
              }
         
     | 
| 
       190 
191 
     | 
    
         | 
| 
       191 
192 
     | 
    
         
             
              object SuccessfulUpload {
         
     | 
| 
       192 
     | 
    
         
            -
                def humanizeUploadSpeed(uploadedBytes: Long, uploadDurations:  
     | 
| 
       193 
     | 
    
         
            -
                  val totalDurationMillis = uploadDurations.foldLeft( 
     | 
| 
       194 
     | 
    
         
            -
                    memo 
     | 
| 
       195 
     | 
    
         
            -
                  } 
     | 
| 
      
 193 
     | 
    
         
            +
                def humanizeUploadSpeed(uploadedBytes: Long, uploadDurations: UploadDuration*): Option[String] = {
         
     | 
| 
      
 194 
     | 
    
         
            +
                  val totalDurationMillis = uploadDurations.foldLeft(0L){ (memo, duration) =>
         
     | 
| 
      
 195 
     | 
    
         
            +
                    memo + duration
         
     | 
| 
      
 196 
     | 
    
         
            +
                  }
         
     | 
| 
       196 
197 
     | 
    
         
             
                  if (totalDurationMillis > 0) {
         
     | 
| 
       197 
198 
     | 
    
         
             
                    val bytesPerMillisecond = uploadedBytes / totalDurationMillis
         
     | 
| 
       198 
199 
     | 
    
         
             
                    val bytesPerSecond = bytesPerMillisecond * 1000 * uploadDurations.length
         
     | 
| 
         @@ -48,7 +48,7 @@ object Site { 
     | 
|
| 
       48 
48 
     | 
    
         
             
                      redirects <- loadRedirects.right
         
     | 
| 
       49 
49 
     | 
    
         
             
                    } yield {
         
     | 
| 
       50 
50 
     | 
    
         
             
                      gzip_zopfli.foreach(_ => logger.info(
         
     | 
| 
       51 
     | 
    
         
            -
                        """| 
     | 
| 
      
 51 
     | 
    
         
            +
                        """|Zopfli is not currently supported. Falling back to regular gzip.
         
     | 
| 
       52 
52 
     | 
    
         
             
                           |If you find a stable Java implementation for zopfli, please send an email to lauri.lehmijoki@iki.fi about it."""
         
     | 
| 
       53 
53 
     | 
    
         
             
                        .stripMargin))
         
     | 
| 
       54 
54 
     | 
    
         
             
                      extensionless_mime_type.foreach(_ => logger.info(
         
     | 
| 
         @@ -7,10 +7,11 @@ import java.util.zip.GZIPOutputStream 
     | 
|
| 
       7 
7 
     | 
    
         
             
            import org.apache.tika.Tika
         
     | 
| 
       8 
8 
     | 
    
         
             
            import s3.website.Ruby._
         
     | 
| 
       9 
9 
     | 
    
         
             
            import s3.website._
         
     | 
| 
       10 
     | 
    
         
            -
            import s3.website.model. 
     | 
| 
      
 10 
     | 
    
         
            +
            import s3.website.model.LocalFile.tika
         
     | 
| 
       11 
11 
     | 
    
         
             
            import s3.website.model.Encoding.encodingOnS3
         
     | 
| 
       12 
12 
     | 
    
         
             
            import java.io.File.createTempFile
         
     | 
| 
       13 
13 
     | 
    
         
             
            import org.apache.commons.io.IOUtils.copy
         
     | 
| 
      
 14 
     | 
    
         
            +
            import scala.util.Try
         
     | 
| 
       14 
15 
     | 
    
         | 
| 
       15 
16 
     | 
    
         
             
            object Encoding {
         
     | 
| 
       16 
17 
     | 
    
         | 
| 
         @@ -34,27 +35,34 @@ object Encoding { 
     | 
|
| 
       34 
35 
     | 
    
         
             
                }
         
     | 
| 
       35 
36 
     | 
    
         
             
            }
         
     | 
| 
       36 
37 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
            sealed trait UploadType  
     | 
| 
      
 38 
     | 
    
         
            +
            sealed trait UploadType {
         
     | 
| 
      
 39 
     | 
    
         
            +
              val pushAction: PushAction
         
     | 
| 
      
 40 
     | 
    
         
            +
            }
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
            case object NewFile extends UploadType {
         
     | 
| 
      
 43 
     | 
    
         
            +
              val pushAction = Created
         
     | 
| 
      
 44 
     | 
    
         
            +
            }
         
     | 
| 
      
 45 
     | 
    
         
            +
            case object FileUpdate extends UploadType {
         
     | 
| 
      
 46 
     | 
    
         
            +
              val pushAction = Updated
         
     | 
| 
      
 47 
     | 
    
         
            +
            }
         
     | 
| 
       38 
48 
     | 
    
         | 
| 
       39 
     | 
    
         
            -
            case object  
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
      
 49 
     | 
    
         
            +
            case object RedirectFile extends UploadType {
         
     | 
| 
      
 50 
     | 
    
         
            +
              val pushAction = Redirected
         
     | 
| 
      
 51 
     | 
    
         
            +
            }
         
     | 
| 
       42 
52 
     | 
    
         | 
| 
       43 
     | 
    
         
            -
            case class  
     | 
| 
      
 53 
     | 
    
         
            +
            case class LocalFile(originalFile: File, uploadType: UploadType)(implicit site: Site) {
         
     | 
| 
       44 
54 
     | 
    
         
             
              lazy val s3Key = site.resolveS3Key(originalFile)
         
     | 
| 
       45 
55 
     | 
    
         | 
| 
       46 
56 
     | 
    
         
             
              lazy val encodingOnS3 = Encoding.encodingOnS3(s3Key)
         
     | 
| 
       47 
57 
     | 
    
         | 
| 
       48 
     | 
    
         
            -
              lazy val lastModified = originalFile.lastModified
         
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
58 
     | 
    
         
             
              /**
         
     | 
| 
       51 
59 
     | 
    
         
             
               * This is the file we should upload, because it contains the potentially gzipped contents of the original file.
         
     | 
| 
       52 
60 
     | 
    
         
             
               *
         
     | 
| 
       53 
61 
     | 
    
         
             
               * May throw an exception, so remember to call this in a Try or Future monad
         
     | 
| 
       54 
62 
     | 
    
         
             
               */
         
     | 
| 
       55 
     | 
    
         
            -
              lazy val uploadFile: File =  
     | 
| 
      
 63 
     | 
    
         
            +
              lazy val uploadFile: Try[File] = LocalFile uploadFile originalFile
         
     | 
| 
       56 
64 
     | 
    
         | 
| 
       57 
     | 
    
         
            -
              lazy val contentType = {
         
     | 
| 
      
 65 
     | 
    
         
            +
              lazy val contentType: Try[String] = tika map { tika =>
         
     | 
| 
       58 
66 
     | 
    
         
             
                val mimeType = tika.detect(originalFile)
         
     | 
| 
       59 
67 
     | 
    
         
             
                if (mimeType.startsWith("text/") || mimeType == "application/json")
         
     | 
| 
       60 
68 
     | 
    
         
             
                  mimeType + "; charset=utf-8"
         
     | 
| 
         @@ -84,24 +92,29 @@ case class LocalFileFromDisk(originalFile: File, uploadType: UploadType)(implici 
     | 
|
| 
       84 
92 
     | 
    
         
             
              /**
         
     | 
| 
       85 
93 
     | 
    
         
             
               * May throw an exception, so remember to call this in a Try or Future monad
         
     | 
| 
       86 
94 
     | 
    
         
             
               */
         
     | 
| 
       87 
     | 
    
         
            -
              lazy val md5 =  
     | 
| 
      
 95 
     | 
    
         
            +
              lazy val md5 = LocalFile md5 originalFile
         
     | 
| 
       88 
96 
     | 
    
         
             
            }
         
     | 
| 
       89 
97 
     | 
    
         | 
| 
       90 
     | 
    
         
            -
            object  
     | 
| 
       91 
     | 
    
         
            -
              lazy val tika = new Tika()
         
     | 
| 
      
 98 
     | 
    
         
            +
            object LocalFile {
         
     | 
| 
      
 99 
     | 
    
         
            +
              lazy val tika = Try(new Tika())
         
     | 
| 
       92 
100 
     | 
    
         | 
| 
       93 
     | 
    
         
            -
              def md5(originalFile: File)(implicit site: Site) = 
     | 
| 
      
 101 
     | 
    
         
            +
              def md5(originalFile: File)(implicit site: Site): Try[MD5] =
         
     | 
| 
      
 102 
     | 
    
         
            +
                uploadFile(originalFile) map { file =>
         
     | 
| 
      
 103 
     | 
    
         
            +
                  using(fis { file }) { DigestUtils.md5Hex }
         
     | 
| 
      
 104 
     | 
    
         
            +
                }
         
     | 
| 
       94 
105 
     | 
    
         | 
| 
       95 
     | 
    
         
            -
              def uploadFile(originalFile: File)(implicit site: Site): File =
         
     | 
| 
      
 106 
     | 
    
         
            +
              def uploadFile(originalFile: File)(implicit site: Site): Try[File] =
         
     | 
| 
       96 
107 
     | 
    
         
             
                encodingOnS3(site resolveS3Key originalFile)
         
     | 
| 
       97 
     | 
    
         
            -
                  .fold(originalFile)(algorithm => 
     | 
| 
       98 
     | 
    
         
            -
                     
     | 
| 
       99 
     | 
    
         
            -
             
     | 
| 
       100 
     | 
    
         
            -
             
     | 
| 
       101 
     | 
    
         
            -
                       
     | 
| 
      
 108 
     | 
    
         
            +
                  .fold(Try(originalFile))(algorithm =>
         
     | 
| 
      
 109 
     | 
    
         
            +
                    Try {
         
     | 
| 
      
 110 
     | 
    
         
            +
                      val tempFile = createTempFile(originalFile.getName, "gzip")
         
     | 
| 
      
 111 
     | 
    
         
            +
                      tempFile.deleteOnExit()
         
     | 
| 
      
 112 
     | 
    
         
            +
                      using(new GZIPOutputStream(new FileOutputStream(tempFile))) { stream =>
         
     | 
| 
      
 113 
     | 
    
         
            +
                        copy(fis(originalFile), stream)
         
     | 
| 
      
 114 
     | 
    
         
            +
                      }
         
     | 
| 
      
 115 
     | 
    
         
            +
                      tempFile
         
     | 
| 
       102 
116 
     | 
    
         
             
                    }
         
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
                  })
         
     | 
| 
      
 117 
     | 
    
         
            +
                  )
         
     | 
| 
       105 
118 
     | 
    
         | 
| 
       106 
119 
     | 
    
         
             
              private[this] def fis(file: File): InputStream = new FileInputStream(file)
         
     | 
| 
       107 
120 
     | 
    
         
             
              private[this] def using[T <: Closeable, R](cl: T)(f: (T) => R): R = try f(cl) finally cl.close()
         
     | 
| 
         @@ -21,13 +21,15 @@ import com.amazonaws.services.cloudfront.model.{CreateInvalidationResult, Create 
     | 
|
| 
       21 
21 
     | 
    
         
             
            import org.mockito.stubbing.Answer
         
     | 
| 
       22 
22 
     | 
    
         
             
            import org.mockito.invocation.InvocationOnMock
         
     | 
| 
       23 
23 
     | 
    
         
             
            import java.util.concurrent.atomic.AtomicInteger
         
     | 
| 
       24 
     | 
    
         
            -
            import org.apache.commons.io.FileUtils. 
     | 
| 
      
 24 
     | 
    
         
            +
            import org.apache.commons.io.FileUtils._
         
     | 
| 
       25 
25 
     | 
    
         
             
            import scala.collection.mutable
         
     | 
| 
       26 
26 
     | 
    
         
             
            import s3.website.Push.{push, CliArgs}
         
     | 
| 
       27 
27 
     | 
    
         
             
            import s3.website.CloudFront.CloudFrontSetting
         
     | 
| 
       28 
28 
     | 
    
         
             
            import s3.website.S3.S3Setting
         
     | 
| 
       29 
29 
     | 
    
         
             
            import org.apache.commons.codec.digest.DigestUtils
         
     | 
| 
       30 
30 
     | 
    
         
             
            import java.util.Date
         
     | 
| 
      
 31 
     | 
    
         
            +
            import s3.website.CloudFront.CloudFrontSetting
         
     | 
| 
      
 32 
     | 
    
         
            +
            import s3.website.S3.S3Setting
         
     | 
| 
       31 
33 
     | 
    
         | 
| 
       32 
34 
     | 
    
         
             
            class S3WebsiteSpec extends Specification {
         
     | 
| 
       33 
35 
     | 
    
         | 
| 
         @@ -799,12 +801,15 @@ class S3WebsiteSpec extends Specification { 
     | 
|
| 
       799 
801 
     | 
    
         
             
              trait EmptySite extends Directories {
         
     | 
| 
       800 
802 
     | 
    
         
             
                type LocalFileWithContent = (String, String)
         
     | 
| 
       801 
803 
     | 
    
         | 
| 
       802 
     | 
    
         
            -
                val localFilesWithContent: mutable.Set[LocalFileWithContent] = mutable.Set()
         
     | 
| 
       803 
804 
     | 
    
         
             
                def setLocalFile(fileName: String) = setLocalFileWithContent((fileName, ""))
         
     | 
| 
       804 
805 
     | 
    
         
             
                def setLocalFiles(fileNames: String*) = fileNames foreach setLocalFile
         
     | 
| 
       805 
     | 
    
         
            -
                def setLocalFileWithContent(fileNameAndContent: LocalFileWithContent) =
         
     | 
| 
       806 
     | 
    
         
            -
                  localFilesWithContent += fileNameAndContent
         
     | 
| 
       807 
806 
     | 
    
         
             
                def setLocalFilesWithContent(fileNamesAndContent: LocalFileWithContent*) = fileNamesAndContent foreach setLocalFileWithContent
         
     | 
| 
      
 807 
     | 
    
         
            +
                def setLocalFileWithContent(fileNameAndContent: LocalFileWithContent) = {
         
     | 
| 
      
 808 
     | 
    
         
            +
                  val file = new File(siteDirectory, fileNameAndContent._1)
         
     | 
| 
      
 809 
     | 
    
         
            +
                  forceMkdir(file.getParentFile)
         
     | 
| 
      
 810 
     | 
    
         
            +
                  file.createNewFile()
         
     | 
| 
      
 811 
     | 
    
         
            +
                  write(file, fileNameAndContent._2)
         
     | 
| 
      
 812 
     | 
    
         
            +
                }
         
     | 
| 
       808 
813 
     | 
    
         
             
                var config = ""
         
     | 
| 
       809 
814 
     | 
    
         
             
                val baseConfig =
         
     | 
| 
       810 
815 
     | 
    
         
             
                """
         
     | 
| 
         @@ -813,21 +818,9 @@ class S3WebsiteSpec extends Specification { 
     | 
|
| 
       813 
818 
     | 
    
         
             
                  |s3_bucket: bucket
         
     | 
| 
       814 
819 
     | 
    
         
             
                """.stripMargin
         
     | 
| 
       815 
820 
     | 
    
         | 
| 
       816 
     | 
    
         
            -
                implicit def cliArgs: CliArgs =  
     | 
| 
      
 821 
     | 
    
         
            +
                implicit def cliArgs: CliArgs = buildCliArgs(config)
         
     | 
| 
       817 
822 
     | 
    
         
             
                def pushMode: PushMode // Represents the --dry-run switch
         
     | 
| 
       818 
823 
     | 
    
         | 
| 
       819 
     | 
    
         
            -
                private def siteWithFilesAndContent(config: String = "", localFilesWithContent: mutable.Set[LocalFileWithContent]): CliArgs = {
         
     | 
| 
       820 
     | 
    
         
            -
                  localFilesWithContent.foreach {
         
     | 
| 
       821 
     | 
    
         
            -
                    case (filePath, content) =>
         
     | 
| 
       822 
     | 
    
         
            -
                      val file = new File(siteDirectory, filePath)
         
     | 
| 
       823 
     | 
    
         
            -
                      forceMkdir(file.getParentFile)
         
     | 
| 
       824 
     | 
    
         
            -
                      file.createNewFile()
         
     | 
| 
       825 
     | 
    
         
            -
                      write(file, content)
         
     | 
| 
       826 
     | 
    
         
            -
                  }
         
     | 
| 
       827 
     | 
    
         
            -
                  localFilesWithContent.clear() // Once we've persisted the changes on the disk, clear the queue. I.e., keep the state where it should be – on the disk.
         
     | 
| 
       828 
     | 
    
         
            -
                  buildCliArgs(config)
         
     | 
| 
       829 
     | 
    
         
            -
                }
         
     | 
| 
       830 
     | 
    
         
            -
             
     | 
| 
       831 
824 
     | 
    
         
             
                private def buildCliArgs(
         
     | 
| 
       832 
825 
     | 
    
         
             
                                config: String = "",
         
     | 
| 
       833 
826 
     | 
    
         
             
                                baseConfig: String =
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: s3_website_monadic
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0.0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.0.38
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Lauri Lehmijoki
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2014- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2014-06-03 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: thor
         
     |